函數(shù)式編程 - 一篇文章概述Functor(函子)、Monad(單子)翰守、Applicative

前言

初步深入函數(shù)式編程是在寒假的時(shí)候孵奶,搞了一本Haskell的書,啃了沒(méi)多久就因?yàn)槲彝蝗坏捻?xiàng)目任務(wù)被擱置了蜡峰,不過(guò)在學(xué)習(xí)的時(shí)候也是各種看不懂了袁,里面的概念略微抽象,再加上當(dāng)時(shí)沒(méi)有適當(dāng)?shù)貙?shí)戰(zhàn)敲Demo湿颅,導(dǎo)致沒(méi)過(guò)多久腦袋就全空了载绿。慶幸的是,Swift是一門高度兼容函數(shù)式編程范式的語(yǔ)言油航,而我又是一只喜歡敲Swift的程序Dog崭庸,在后來(lái)我使用Swift編碼時(shí),有意識(shí)或無(wú)意識(shí)地套用函數(shù)式編程范式的一些概念谊囚,也漸漸加深我對(duì)函數(shù)式編程的理解怕享。這篇文章是我對(duì)自己所掌握的函數(shù)式編程的一個(gè)小總結(jié),主要探討的是函數(shù)式編程中的幾個(gè)概念: Functor镰踏、Applicative函筋、Monad以及它們?cè)赟wift中的表現(xiàn)形式。由于本人能力有限奠伪,一些概念上的不嚴(yán)謹(jǐn)跌帐、編碼上的不全面希望大家多包涵,歡迎留下各位寶貴的意見(jiàn)或問(wèn)題绊率。

本文為純概念講述谨敛,后期或許會(huì)有函數(shù)式編程實(shí)戰(zhàn)的文章推出(我有空寫再說(shuō)吧)

概念

Context

在編碼時(shí),我們會(huì)遇到各種數(shù)據(jù)類型滤否,基礎(chǔ)的數(shù)據(jù)類型我們稱作脸狸,當(dāng)然這并不是指編程語(yǔ)言中的基本數(shù)據(jù)類型,比如說(shuō)整形1它可以稱作一個(gè)值藐俺,一個(gè)結(jié)構(gòu)體struct Person { let name: String; let age: Int }的實(shí)例也可以成為一個(gè)值肥惭,那么何為Context(上下文)呢盯仪,我們可以將它理解為對(duì)值的一個(gè)包裝紊搪,通過(guò)這層包裝蜜葱,我們可以得知值此時(shí)所處在的一個(gè)狀態(tài)。在Haskell中耀石,這個(gè)包裝就是typeclass(類型類)牵囤,而在Swift中,魔性的enum(枚舉)可以充當(dāng)這個(gè)角色滞伟,一個(gè)例子揭鳞,就是Swift中的Optional(可選類型),它的定義如下(相關(guān)繼承或協(xié)議關(guān)系在這里不標(biāo)出):

Optional<Wrapped> {
    case none
    case some(Wrapped)

Optional有兩種狀態(tài)梆奈,一種是空狀態(tài)none野崇,也就是和平時(shí)我們傳入的nil相等價(jià),一種是存在值的狀態(tài)亩钟,泛型Wrapped指代被包入這層上下文的值的類型乓梨。通過(guò)這個(gè)例子,我們可以很直觀地理解Context:描述值在某一階段的狀態(tài)清酥。當(dāng)然扶镀,在平時(shí)開(kāi)發(fā)中,我們會(huì)見(jiàn)到各種Context焰轻,比如Either:

enum Either<L, R> {
    case left(L)
    case right(R)
}

它代表在某個(gè)階段值可能在left或者right中存在臭觉。
在一些函數(shù)式響應(yīng)式編程框架如ReactiveCocoaRxSwift中辱志,Context無(wú)處不在:RACSignal蝠筑、Observable,甚至是Swift的基本類型Array(數(shù)組)它本身也可以看作是一個(gè)Context揩懒∈惨遥可見(jiàn),只要你接觸了函數(shù)式編程旭从,Context即會(huì)接觸稳强。

這里,我特別說(shuō)下這個(gè)Context:Result和悦,因?yàn)樵诤竺鎸?duì)其他概念以及實(shí)戰(zhàn)的講述中我都會(huì)以它為基礎(chǔ):

enum Result<T> {
    case success(T)
    case failure(MyError)
}

Result上下文存在兩種狀態(tài)退疫,一種是成功的狀態(tài),當(dāng)處于這個(gè)狀態(tài)鸽素,Result就會(huì)持有一個(gè)特定類型的值褒繁,另外一種狀態(tài)是失敗狀態(tài),在這個(gè)狀態(tài)中馍忽,你可以獲取到一個(gè)錯(cuò)誤的實(shí)例(這個(gè)實(shí)例可以是你自己擬定的)棒坏。這么這個(gè)Context有什么用呢燕差?想象一下,你正在進(jìn)行一項(xiàng)網(wǎng)絡(luò)操作坝冕,獲取到的數(shù)據(jù)是無(wú)法確定的徒探,你或許能如你所愿,從服務(wù)器中獲取到你期望的值喂窟,但是也有可能此時(shí)服務(wù)器發(fā)生一些未知的錯(cuò)誤测暗,或者網(wǎng)絡(luò)延時(shí),又或是一些不可抗力的影響磨澡,那么碗啄,此時(shí)你得到的將會(huì)是一個(gè)錯(cuò)誤的表示,如HTTP Code 500...而Result可以在這種情況下引入來(lái)表示你在網(wǎng)絡(luò)操作中獲取到的最終結(jié)果稳摄,是成功還是失敗稚字。除了網(wǎng)絡(luò)請(qǐng)求,諸如數(shù)據(jù)庫(kù)操作厦酬、數(shù)據(jù)解析等等胆描,Result都可以引入來(lái)進(jìn)行更明確的標(biāo)示。

何為Functor弃锐、Applicative袄友、Monad?

你可以把FunctorApplicative霹菊、Monad想象成Swift中的Protocol(協(xié)議)剧蚣,它們可以為某種數(shù)據(jù)結(jié)構(gòu)的抽象,而這種數(shù)據(jù)接口正是剛剛我在上面提到的Context旋廷,要將某個(gè)Context實(shí)現(xiàn)成Functor鸠按、Applicative饶碘、Monad,你必須實(shí)現(xiàn)其中特定的函數(shù)扎运,所以,要了解什么是Functor洞拨、ApplicativeMonad烦衣,你需要知道它們定義了那些協(xié)議函數(shù)。接下來(lái)我會(huì)一一講解秸歧。

Functor

我們對(duì)一個(gè)值的運(yùn)算操作使用的是函數(shù),比如我要對(duì)一個(gè)整形的值進(jìn)行翻倍操作键菱,我們可以定義一個(gè)函數(shù):

func double(_ value: Int) -> Int {
    return 2 * value
}

然后就可以拿這個(gè)函數(shù)對(duì)特定的值進(jìn)行操作:

let a = 2
let b = double(a)

好矾麻,問(wèn)題來(lái)了纱耻,如果此時(shí)這個(gè)值被包在一個(gè)Context中呢?
一個(gè)函數(shù)只能作用于它聲明好的特定類型的值险耀,運(yùn)算整形的函數(shù)不能用來(lái)運(yùn)算一個(gè)非整形的Context,所以這時(shí)玖喘,我們引入了Functor甩牺。它要做的,就是使一個(gè)只能運(yùn)算值的函數(shù)用來(lái)運(yùn)算一個(gè)包有這個(gè)值類型的Context累奈,最后返回的一個(gè)包有運(yùn)算結(jié)果的Context贬派,為此,我們要實(shí)現(xiàn)map這個(gè)函數(shù)(在Haskell中為fmap)澎媒,它的偽代碼是這樣的:
Context(結(jié)果值) = map(Context(初始值), 運(yùn)算函數(shù))

現(xiàn)在我們拿Result來(lái)實(shí)現(xiàn)一下:

extension Result {
    func map<O>(_ mapper: (T) -> O) -> Result<O> {
        switch self {
        case .failure(let error):
            return .failure(error)
        case .success(let value):
            return .success(mapper(value))
        }
    }
}

我們可以看到搞乏,首先我們對(duì)Result進(jìn)行模式匹配,當(dāng)此時(shí)狀態(tài)是失敗的話戒努,我們也直接返回失敗请敦,并把錯(cuò)誤的實(shí)例傳遞下去,如果狀態(tài)是成功的储玫,我們就對(duì)初始的值進(jìn)行運(yùn)算侍筛,最后返回包有結(jié)果值的成功狀態(tài)。
為了后面表達(dá)式簡(jiǎn)便撒穷,我在這里定義了map的運(yùn)算符<^>

precedencegroup ChainingPrecedence {
    associativity: left
    higherThan: TernaryPrecedence
}

// Functor
infix operator <^> : ChainingPrecedence

// For Result
func <^><T, O>(lhs: (T) -> O, rhs: Result<T>) -> Result<O> {
    return rhs.map(lhs)
}

我們現(xiàn)在就可以測(cè)試一下:

let a: Result<Int> = .success(2)
let b = double <^> a

在上面我提到匣椰,Swift的數(shù)組也可以當(dāng)成是Context,它是作為一個(gè)包有多個(gè)值的狀態(tài)存在端礼。想必在日常開(kāi)發(fā)中我們經(jīng)常也用到了Swift數(shù)組中的map函數(shù)吧:

let arrA = [1, 2, 3, 4, 5]
let arrB = arrA.map(double)

RxSwift中我們也經(jīng)常使用map

let ob = Observable.just(1).map(double)

Applicative

Applicative其實(shí)就是高級(jí)的Functor禽笑,我們可以調(diào)出上面Functormap偽代碼:
Context(結(jié)果值) = map(Context(初始值), 運(yùn)算函數(shù))
在函數(shù)式編程中,函數(shù)也可以作為一個(gè)值來(lái)看待蛤奥,若此時(shí)這個(gè)函數(shù)也是被一個(gè)Context包裹的佳镜,單純的map是不能接受包裹著函數(shù)的Context,所以我們引入了Applicative
Context(結(jié)果值) = apply(Context(初始值), Context(運(yùn)算函數(shù)))

我們將Result實(shí)現(xiàn)Applicative

extension Result {
    func apply<O>(_ mapper: Result<(T) -> O>) -> Result<O> {
        switch mapper {
        case .failure(let error):
            return .failure(error)
        case .success(let function):
            return self.map(function)
        }
    }
}

// Applicative
infix operator <*> : ChainingPrecedence

// For Result
func <*><T, O>(lhs: Result<(T) -> O>, rhs: Result<T>) -> Result<O> {
    return rhs.apply(lhs)
}

使用:

let function: Result<(Int) -> Int> = .success(double)
let a: Result<Int> = .success(2)
let b = function <*> a

Applicative在日常開(kāi)發(fā)中其實(shí)用的不多喻括,很多時(shí)候我們并不會(huì)將一個(gè)函數(shù)塞進(jìn)一個(gè)Context上邀杏,但是如果你用了一些略為高階的函數(shù)時(shí),它強(qiáng)勁的能力就能在此時(shí)表現(xiàn)出來(lái)望蜡,這里舉一個(gè)略為晦澀的例子脖律,你可以花點(diǎn)時(shí)間搞懂它:
這個(gè)例子的思路是來(lái)自源Swift的函數(shù)式JSON解析庫(kù)Argo的基本用法小泉,若大家有興趣可以閱讀下Argo的源碼: thoughtbot/Argo

假設(shè)現(xiàn)在我定義了一個(gè)函數(shù)酸茴,它能夠接受一個(gè)Any的JSON Object薪捍,以及一個(gè)值在JSON中對(duì)應(yīng)的Key(鍵)作為參數(shù)酪穿,返回一個(gè)從JSON數(shù)據(jù)中解析出來(lái)的結(jié)果被济,由于這個(gè)結(jié)果是不確定的只磷,可能JSON中不存在此鍵對(duì)應(yīng)的值喳瓣,所以我們用Result來(lái)包裝它畏陕,這個(gè)函數(shù)的簽名為:

func parse<T>(jsonObject: Any, key: String) -> Result<T>

當(dāng)解析成功時(shí)惠毁,返回的Result處于成功狀態(tài)鞠绰,當(dāng)解析失敗時(shí)飒焦,返回的Result處于失敗狀態(tài)并攜帶錯(cuò)誤的實(shí)體,我們能夠通過(guò)錯(cuò)誤實(shí)體得知解析失敗的原因驴一。

現(xiàn)在我們有一個(gè)結(jié)構(gòu)體肝断,它里面有多個(gè)成員胸懈,它實(shí)現(xiàn)了默認(rèn)的構(gòu)造器:

struct Person {
    let name: String
    let age: Int
    let from: String
}

我們自己可以編寫一套函數(shù)柯里化的庫(kù)趣钱,這個(gè)庫(kù)能夠?qū)Χ鄥?shù)的函數(shù)進(jìn)行柯里化羔挡,你也可以從Github中下載: thoughtbot/Curry
比如,我們有一個(gè)函數(shù)呈野,它的基本簽名是: func haha(a: Int, b: Int, c: Int) -> Int被冒,通過(guò)函數(shù)柯里化我們可以將其轉(zhuǎn)化為(Int) -> (Int) -> (Int) -> Int類型的函數(shù)昨悼。
我們此時(shí)將Person的構(gòu)造器進(jìn)行函數(shù)柯里化:curry(Person.init)率触,此時(shí)我們得到的是類型為(String) -> (Int) -> (String) -> Person的值葱蝗。
現(xiàn)在奇幻的魔法來(lái)了两曼,我定義一個(gè)將JSON解析成Person的函數(shù):

func parseJSONToPerson(json: Any) -> Result<Person> {
    return curry(Person.init)
        <^> parse(jsonObject: json, key: "name")
        <*> parse(jsonObject: json, key: "age")
        <*> parse(jsonObject: json, key: "from")
}

通過(guò)這個(gè)函數(shù),我能夠?qū)⒁粋€(gè)JSON數(shù)據(jù)解析成Person的實(shí)例户辫,以一個(gè)Result的包裝返回,如果解析失敗捺萌,Result處理失敗狀態(tài)會(huì)攜帶一個(gè)錯(cuò)誤的實(shí)例。

這個(gè)函數(shù)為什么可以這么寫呢披坏,我們來(lái)分解一下:
首先通過(guò)函數(shù)的柯里化我們得到了類型為(String) -> (Int) -> (String) -> Person的值棒拂,它也是一個(gè)函數(shù)帚屉,然后經(jīng)過(guò)了<^>map的操作,map的右邊是一個(gè)解析了name返回的Result喻旷,它的類型為Result<String>且预,map將函數(shù)(String) -> (Int) -> (String) -> Person應(yīng)用于Result<String>,此時(shí)我們得到的是返回的結(jié)果(Int) -> (String) -> Person的Result包裝:Result<(Int) -> (String) -> Person>(因?yàn)橐呀?jīng)消費(fèi)掉了一個(gè)參數(shù))涮拗,此時(shí)多搀,這個(gè)函數(shù)就被一個(gè)Context包裹住了康铭,后面我們不能再用map去將這個(gè)函數(shù)應(yīng)用在接下來(lái)解析出來(lái)的數(shù)據(jù)了从藤,所以這是我們就借助于Applicative<*>夷野,接下來(lái)看第二個(gè)參數(shù)骑丸,parse函數(shù)將JSON解析返回了類型為Result<Int>的結(jié)果通危,我們通過(guò)<*>Result<(Int) -> (String) -> Person>的函數(shù)取出來(lái)菊碟,應(yīng)用于Result<Int>,就得到了類型為Result<(String) -> Person>的結(jié)果蚣驼。以此類推梅垄,最終我們就獲取到了經(jīng)JSON解析后的結(jié)果Result<Person>输玷。
Applicative強(qiáng)大的能力能夠讓代碼變得如此優(yōu)雅欲鹏,這就是函數(shù)式編程的魅力之所在臭墨。

Monad

Monad中文稱為單子尤误,網(wǎng)上看到挺多人被Monad的概念所搞暈损晤,其實(shí)它也是基于上面所講述的概念而來(lái)的尤勋。對(duì)于使用過(guò)函數(shù)式響應(yīng)式編程框架(Rx系列[RxSwift瘦棋、RxJava]赌朋、ReactiveCocoa)的人來(lái)說(shuō)沛慢,可能不知道Monad是什么,但是在實(shí)戰(zhàn)中肯定用過(guò)往枣,它所要求實(shí)現(xiàn)的函數(shù)說(shuō)白了就是flatMap

let ob = Observable.just(1).flatMap { num in
    Observable.just("The number is \(num)")
}

有很多人喜歡用降維來(lái)形容flatMap的能力伐庭,但是,它能做的分冈,不止如此圾另。
Monad需要實(shí)現(xiàn)的函數(shù)我們可以稱為bind,在Haskell中它使用符號(hào)>>=雕沉,在Swift中我們可以定義運(yùn)算符>>-來(lái)表示bind函數(shù)集乔,或者直接叫做flatMap。我們先來(lái)看看他的偽代碼:
首先我們定義一個(gè)函數(shù)坡椒,他的作用是將一個(gè)值進(jìn)行包裝扰路,這里標(biāo)示出這個(gè)函數(shù)的簽名:
function :: 值A(chǔ) -> Context(值B)(值A(chǔ)與值B的類型可相同亦可不同)
我們的bind函數(shù)就可以這么寫了:
Context(結(jié)果值) = Context(初始值) >>- function
這里我們實(shí)現(xiàn)一下ResultMonad

extension Result {
    func flatMap<O>(_ mapper: (T) -> Result<O>) -> Result<O> {
        switch self {
        case .failure(let error):
            return .failure(error)
        case .success(let value):
            return mapper(value)
        }
    }
}

// Monad
infix operator >>- : ChainingPrecedence

// For Result
func >>-<T, O>(lhs: Result<T>, rhs: (T) -> Result<O>) -> Result<O> {
    return lhs.flatMap(rhs)
}

Monad的定義很簡(jiǎn)單汗唱,但是Monad究竟能幫我們解決什么問(wèn)題呢?它要怎么使用呢框弛?別急榜旦,通過(guò)以下這個(gè)例子咐旧,你就能對(duì)Monad有更深一層的理解:
假設(shè)現(xiàn)在我有一系列的操作:

  1. 通過(guò)特定條件進(jìn)行本地?cái)?shù)據(jù)庫(kù)的查詢,找出相關(guān)的數(shù)據(jù)
  2. 利用上面從數(shù)據(jù)庫(kù)得到的數(shù)據(jù)作為參數(shù)屡律,向服務(wù)器發(fā)起請(qǐng)求霍殴,獲取響應(yīng)數(shù)據(jù)
  3. 將從網(wǎng)絡(luò)獲取到的原始數(shù)據(jù)轉(zhuǎn)換成JSON數(shù)據(jù)
  4. 將JSON數(shù)據(jù)進(jìn)行解析面睛,返回最終解析完成的有特定類型的實(shí)體

對(duì)以上操作的分析但壮,我們能得知以上每一個(gè)操作它的最終結(jié)果都具有不確定性,意思就是說(shuō)我們無(wú)法保證操作百分百完成,能成功返回我們想要的數(shù)據(jù)剃根,所以我們很容易就會(huì)想到利用上面已經(jīng)定義的Context:Reuslt將獲取到的結(jié)果進(jìn)行包裹舔糖,若獲取結(jié)果成功,Result將攜帶結(jié)果值處于成功狀態(tài)夕凝,若獲取結(jié)果失敗,Result將攜帶錯(cuò)誤的信息處于失敗狀態(tài)晋控。
現(xiàn)在狂男,我們針對(duì)以上每種操作進(jìn)行函數(shù)定義:

// A代表從數(shù)據(jù)庫(kù)查找數(shù)據(jù)的條件的類型
// B代表期望數(shù)據(jù)庫(kù)返回結(jié)果的類型
func fetchFromDatabase(conditions: A) -> Result<B> { ... }

// B類型作為網(wǎng)絡(luò)請(qǐng)求的參數(shù)類型發(fā)起網(wǎng)絡(luò)請(qǐng)求
// 獲取到的數(shù)據(jù)為C類型蔑穴,可能是原始字符串或者是二進(jìn)制
func requestNetwork(parameters: B) -> Result<C> { ... }

// 將獲取到的原始數(shù)據(jù)類型轉(zhuǎn)換成JSON數(shù)據(jù)
func dataToJSON(data: C) -> Result<JSON> { ... }

// 將JSON進(jìn)行解析輸出實(shí)體
func parse(json: JSON) -> Result<Entity> { ... }

現(xiàn)在我們假設(shè)所有的操作都是在同一條線程中進(jìn)行的(非UI線程),如果我們只是純粹地用基本的方法去調(diào)用這些函數(shù),我們可能要這么來(lái):

var entityResult: Entity?
if case .success(let b) = fetchFromDatabase(conditions: XXX) {
    if case .success(let c) = requestNetwork(parameters: b) {
        if case .success(let json) = dataToJSON(data: c) {
            if case .success(let entity) = parse(json: json) {
                entityResult = entity
            }
        }
    }
}

這代碼寫起來(lái)也好看起來(lái)也好真的是一把辛酸淚啊涯呻,而且涝登,這里還有一個(gè)缺陷剑刑,就是我們無(wú)法從中獲取到錯(cuò)誤的信息,如果我們還想要獲取到錯(cuò)誤的信息蔑赘,必須再編寫多一大串代碼了。

此時(shí)尉辑,Monad出場(chǎng)了:

let entityResult = fetchFromDatabase(conditions: XXX) >>- requestNetwork >>- dataToJSON >>- parse

嚇到了吧曼振,只需一行代碼,即可將所有要做的事情連串起來(lái)了蔚龙,并且冰评,最終我們獲取到的是經(jīng)Result包裝的數(shù)據(jù),若在操作的過(guò)程中發(fā)生錯(cuò)誤府蛇,錯(cuò)誤的信息也記錄在里面了集索。
這就是Monad的威力

當(dāng)然,我們可以繼續(xù)對(duì)上面的操作進(jìn)行優(yōu)化汇跨,比如說(shuō)現(xiàn)在我需要在網(wǎng)絡(luò)請(qǐng)求的函數(shù)中加多一個(gè)參數(shù)务荆,表示請(qǐng)求的URL,我們可以這樣來(lái)定義這個(gè)網(wǎng)絡(luò)請(qǐng)求函數(shù):

// B類型作為網(wǎng)絡(luò)請(qǐng)求的參數(shù)類型發(fā)起網(wǎng)絡(luò)請(qǐng)求
// 獲取到的數(shù)據(jù)為C類型穷遂,可能是原始字符串或者是二進(jìn)制
func requestNetwork(urlString: String) -> (B) -> Result<C> {
    return { parameters in
        return { ... }
    }
}

調(diào)用的時(shí)候我們只需要這樣調(diào)用:

let entityResult = fetchFromDatabase(conditions: XXX) >>- requestNetwork(urlString: "XXX.com/XXX/XXX") >>- dataToJSON >>- parse

這主要是高階函數(shù)的使用技巧函匕。

個(gè)人對(duì)Monad作用的總結(jié)有兩部分:

  1. 對(duì)一系列針對(duì)值與Context的操作進(jìn)行鏈?zhǔn)浇Y(jié)合,代碼極其優(yōu)雅蚪黑,清晰明了盅惜。
  2. 將值與Context之間的轉(zhuǎn)換、Context內(nèi)部進(jìn)行的操作對(duì)外屏蔽忌穿,像上面我用原始的方式進(jìn)行操作抒寂,我們需要手動(dòng)地分析Context的情況,手動(dòng)地針對(duì)不同的Context狀態(tài)進(jìn)行相應(yīng)的操作掠剑,而如果我們使用Monad屈芜,整一流程下來(lái)我們什么都不需要做,坐享其成朴译,取得最終的結(jié)果井佑。

總結(jié)

Swift是一門高度適配函數(shù)式編程范式的語(yǔ)言,你可以在里面到處都能找到函數(shù)式編程思想的身影眠寿,通過(guò)上面對(duì)Functor躬翁、AppliactiveMonad相關(guān)概念的講述盯拱,在鞏固我對(duì)函數(shù)式編程的知識(shí)外盒发,希望也能讓你對(duì)函數(shù)式編程的理解有幫助例嘱,若文章有概念不嚴(yán)謹(jǐn)?shù)牡胤交蛘咤e(cuò)誤,望見(jiàn)諒宁舰,也希望能夠向我提出蝶防。
謝謝閱讀。

參考鏈接

阮一峰的網(wǎng)絡(luò)日志 - 圖解 Monad

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末明吩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子殷费,更是在濱河造成了極大的恐慌印荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件详羡,死亡現(xiàn)場(chǎng)離奇詭異仍律,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)实柠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門水泉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人窒盐,你說(shuō)我怎么就攤上這事草则。” “怎么了蟹漓?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵炕横,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我葡粒,道長(zhǎng)份殿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任嗽交,我火速辦了婚禮卿嘲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夫壁。我一直安慰自己拾枣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布掌唾。 她就那樣靜靜地躺著放前,像睡著了一般。 火紅的嫁衣襯著肌膚如雪糯彬。 梳的紋絲不亂的頭發(fā)上凭语,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音撩扒,去河邊找鬼似扔。 笑死吨些,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炒辉。 我是一名探鬼主播豪墅,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼黔寇!你這毒婦竟也來(lái)了偶器?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缝裤,失蹤者是張志新(化名)和其女友劉穎屏轰,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憋飞,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霎苗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榛做。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁盏。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖检眯,靈堂內(nèi)的尸體忽然破棺而出厘擂,到底是詐尸還是另有隱情,我是刑警寧澤轰传,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布驴党,位于F島的核電站,受9級(jí)特大地震影響获茬,放射性物質(zhì)發(fā)生泄漏港庄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一恕曲、第九天 我趴在偏房一處隱蔽的房頂上張望鹏氧。 院中可真熱鬧,春花似錦佩谣、人聲如沸把还。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吊履。三九已至,卻和暖如春调鬓,著一層夾襖步出監(jiān)牢的瞬間艇炎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工腾窝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀踪,地道東北人居砖。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驴娃,于是被迫代替她去往敵國(guó)和親奏候。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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