(Haskell主義亡我Rust之心不死啊!)這篇文章也許可以幫助你理解Monad。

作者在前文: Rust中Functor的可行性中分析了Rust中實現Functor的可行性。

起因是Rust官方團隊的withoutboats在實現非同步的過程中,發了一系列的推文說了Rust中並不能像Haskell的do-notation來實現非同步,而是用async/await來代替的原因。

該文作者寫文章分析了Rust中實現functor的可行性,闡述了一個重要的觀點:

Rust將效能作為最高優先順序,所以就不可能像Haskell那樣過多地傾向於型別的抽象,而是更傾向於使用trait作為抽象。所以在以效能優先的考慮下,就沒有提供haskell那樣的HKT,所以不能使用monad來實現Future。

原文:Read More

理論上,future/promise也可以看作是monad是一個例項:

—— Monad is just a fancy name for the Wrapper interface above

class

Monad

m

where

return

::

a

->

m

a

—— wrap

>>=

::

m

a

->

a

->

m

b

->

m

b

—— chain

—— Promises are monads, so we can make an instance for it

instance

Monad

Promise

where

return

value

=

Promise

resolve

value

value

>>=

callback

=

Promise

then

value

callback

—— Then the do-syntax becomes equivalent to async/await

getBalances

::

Promise

Map

String

String

getBalances

=

do

accounts

<-

getAccounts

balances

<-

getBalance

accounts

return

Map

fromList

zip

accounts

balances

))

上面是使用monad實現promise的一個示例(來源)。

所以,作者在前文中提供了一個基於trait的Functor和Monad實現(高階trait),純粹是作為學術上的探索。

在今天這篇文章中,作者進一步探索了do-notation在Rust中的實現。

do-notation是Haskell中的語法,用於操作Monad,而不再需要很長的巢狀函式。但目前在Rust中實現do-notation的問題在於:Rust的閉包並不遵循TCP(Tennent一致性原則,Tennent’s Correspondence Principle)。

// This function returns `5`。

fn

early_return

()

->

u8

{

return

5

0

}

該函式會返回5,但是在換成閉包,就得到另外的結果:

// This function returns `0`。

fn

abstracted_early_return

()

->

u8

{

||

return

5

)();

0

}

這裡

(|| return 5)();

返回的是

()

return

在這裡沒有作用。這就是withoutboat在推文中所說的:

Rust’s imperative control flow statements like

return

and

break

inside of do notation also doesn’t make sense, because we do not have TCP preserving closures。

所以,作者嘗試在給出一種解決方案,實現一個宏:

do

{

expr1

let

a

=

expr2

expr3

let

b

=

expr4

expr5

expr6

a

b

);

return

expr7

}

等價於:

surface

{

expr1

bubble

expr2

bind

|

a

|

{

expr3

bubble

expr4

bind

|

b

|

{

expr5

expr6

a

b

);

Monad

::

unit

expr7

})

})

}

其中bubble!等價於:

match

expr

{

ControlFlow

::

Return

_

=>

return

expr

ControlFlow

::

Break

Some

_

))

=>

break

expr

ControlFlow

::

Break

None

=>

break

ControlFlow

::

Continue

=>

continue

ControlFlow

::

Value

_

=>

expr

// We‘d also want `Yield` here eventually,

// but that comes with its own problems,

// which is a story for another time。

}

surface! block

等價於:

enum

ControlFlow

<

T

>

{

Return

T

),

Break

Option

<

T

>

),

Continue

// `Value` simply means we’re forwarding

// the value without effecting any control

// flow。

Value

T

),

}

match

||

block

)()

{

ControlFlow

::

Return

t

=>

return

t

ControlFlow

::

Break

Some

t

))

=>

break

t

ControlFlow

::

Break

None

=>

break

ControlFlow

::

Continue

=>

continue

ControlFlow

::

Value

t

=>

t

}

透過自定義的ControlFlow,捕獲閉包的控制流,並且在其外對其進行轉發,假裝從閉包控制流中逃脫,這就是一種do-notation。

但這種實現方式效能上還有問題。也有人在論壇裡提出pre-RFC讓Rust實現TCP式閉包,比如:

fn

main

(){

// `return` would be a divergent expression and return from `main`

let

id

i32

=

“321”

parse

()。

or_else

||

return

);

}

允許從閉包中返回。但是官方並不同意這樣做,主要理由有兩點:

首先,這樣肯定會破壞現有的Rust生態

其次,如果允許閉包在堆疊之間跳轉則會放大「異常安全」的問題。

所以,官方應該是不會支援TCP閉包的。

另外,在Redox專案裡實現了一個crate,來達成這種效果:control_flow ,但該包也有說明:除非你真的需要它的時候,你再用它。