3 回答

TA貢獻(xiàn)1811條經(jīng)驗(yàn) 獲得超5個(gè)贊
更新:這個(gè)問題是一個(gè)非常長(zhǎng)的博客系列的主題,您可以在Monads上閱讀它 -感謝您提出的偉大問題!
用OOP程序員會(huì)理解的術(shù)語(沒有任何函數(shù)式編程背景),什么是monad?
monad是一種類型的“放大器”,它遵循某些規(guī)則并提供某些操作。
首先,什么是“類型放大器”?我的意思是說,有一個(gè)系統(tǒng)可以讓您選擇一種類型并將其轉(zhuǎn)換為更特殊的類型。例如,在C#中考慮Nullable<T>。這是一種放大器。它使您可以使用一個(gè)類型,例如int,并為該類型添加新功能,即現(xiàn)在可以在以前無法使用時(shí)為null。
作為第二個(gè)示例,請(qǐng)考慮IEnumerable<T>。它是一種類型的放大器。它允許您采用一個(gè)類型,例如,string并為該類型添加新功能,即,您現(xiàn)在可以從任意數(shù)量的單個(gè)字符串中構(gòu)成一個(gè)字符串序列。
什么是“某些規(guī)則”?簡(jiǎn)而言之,存在一種合理的方式,使基礎(chǔ)類型上的功能在放大類型上起作用,從而使它們遵循功能組成的正常規(guī)則。例如,如果您有一個(gè)整數(shù)函數(shù),請(qǐng)說
int M(int x) { return x + N(x * 2); }
則相應(yīng)的on函數(shù)Nullable<int>可以使所有運(yùn)算符和其中的調(diào)用與以前一樣“以相同的方式”協(xié)同工作。
(這是非常模糊和不精確的;您要求提供一種解釋,該解釋沒有假定任何有關(guān)功能組成的知識(shí)。)
什么是“操作”?
有一個(gè)“單位”操作(有時(shí)也稱為“返回”操作),該操作從普通類型獲取值并創(chuàng)建等效的單價(jià)值。本質(zhì)上,這提供了一種獲取未放大類型的值并將其轉(zhuǎn)換為放大類型的值的方法??梢詫⑵鋵?shí)現(xiàn)為OO語言的構(gòu)造函數(shù)。
有一個(gè)“綁定”操作,它接受一個(gè)單子值和一個(gè)可以轉(zhuǎn)換該值并返回新單子值的函數(shù)。綁定是定義monad語義的關(guān)鍵操作。它使我們可以將未放大類型的操作轉(zhuǎn)換為對(duì)放大類型的操作,這要遵循前面提到的功能組成規(guī)則。
通常有一種方法可以使未放大類型從放大類型中退回。嚴(yán)格來說,此操作不需要具有monad。(盡管如果您想擁有自己的名字是很有必要的。我們將不在本文中進(jìn)一步討論。)
再以Nullable<T>一個(gè)例子為例。您可以使用構(gòu)造函數(shù)將int轉(zhuǎn)換為Nullable<int>。C#編譯器會(huì)為您處理大多數(shù)可為空的“提升”,但是如果沒有,提升轉(zhuǎn)換將非常簡(jiǎn)單:例如,
int M(int x) { whatever }
變成
Nullable<int> M(Nullable<int> x)
{
if (x == null)
return null;
else
return new Nullable<int>(whatever);
}
并通過該屬性將其變Nullable<int>回原樣。intValue
函數(shù)轉(zhuǎn)換是關(guān)鍵。注意,在轉(zhuǎn)換中如何捕獲可為空操作的實(shí)際語義,即可null傳播操作的語義null。我們可以對(duì)此進(jìn)行概括。
假設(shè)您具有從int到的功能int,就像我們?cè)瓉淼墓δ芤粯覯。您可以輕松地將其轉(zhuǎn)換為接受an int并返回a 的函數(shù),Nullable<int>因?yàn)槟梢酝ㄟ^可為null的構(gòu)造函數(shù)運(yùn)行結(jié)果?,F(xiàn)在假設(shè)您具有以下高階方法:
static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
if (amplified == null)
return null;
else
return func(amplified.Value);
}
看到你能做什么?現(xiàn)在,任何采用an int并返回an int或采用an int并返回a的Nullable<int>方法現(xiàn)在都可以應(yīng)用可空語義。
此外:假設(shè)您有兩種方法
Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }
而您想組成它們:
Nullable<int> Z(int s) { return X(Y(s)); }
即和Z的組成。但是您不能這樣做,因?yàn)樾枰粋€(gè),然后返回一個(gè)。但是,由于您具有“綁定”操作,因此可以進(jìn)行以下工作:XYXintYNullable<int>
Nullable<int> Z(int s) { return Bind(Y(s), X); }
對(duì)單聲道的綁定操作使放大類型上的功能組合起作用。我上面揮揮手的“規(guī)則”是單子保留正常功能組成的規(guī)則。與標(biāo)識(shí)函數(shù)組成的結(jié)果將產(chǎn)生原始函數(shù),該組成具有關(guān)聯(lián)性,依此類推。
在C#中,“綁定”稱為“ SelectMany”??匆幌滤绾卧谛蛄衜onad上工作。我們需要做兩件事:將一個(gè)值轉(zhuǎn)換為一個(gè)序列,然后對(duì)序列進(jìn)行綁定操作。作為獎(jiǎng)勵(lì),我們還具有“將序列變回值”的功能。這些操作是:
static IEnumerable<T> MakeSequence<T>(T item)
{
yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
// let's just take the first one
foreach(T item in sequence) return item;
throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
foreach(T item in seq)
foreach(T result in func(item))
yield return result;
}
可為空的monad規(guī)則是“將產(chǎn)生可為空的兩個(gè)函數(shù)組合在一起,檢查內(nèi)部函數(shù)是否為null;如果滿足,則產(chǎn)生null,否則為null,然后用結(jié)果調(diào)用外部函數(shù)”。這是可為空的所需語義。
序列monad規(guī)則是“將產(chǎn)生序列的兩個(gè)函數(shù)組合在一起,將外部函數(shù)應(yīng)用于內(nèi)部函數(shù)產(chǎn)生的每個(gè)元素,然后將所有結(jié)果序列連接在一起”。Bind/ SelectMany方法捕獲了monad的基本語義;這是告訴您monad真正含義的方法。
我們可以做得更好。假設(shè)您有一個(gè)整數(shù)序列,以及一個(gè)采用整數(shù)并產(chǎn)生字符串序列的方法。我們可以對(duì)綁定操作進(jìn)行一般化,以允許接受和返回不同放大類型的函數(shù)的組合,只要其中一個(gè)的輸入與另一個(gè)的輸出匹配即可:
static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
foreach(T item in seq)
foreach(U result in func(item))
yield return result;
}
因此,現(xiàn)在我們可以說:“將這組單個(gè)整數(shù)放大為整數(shù)序列。將該特定整數(shù)轉(zhuǎn)換為一串字符串,放大為一系列字符串?,F(xiàn)在將這兩個(gè)操作放在一起:將這組整數(shù)放大為”所有的字符串序列?!?Monads使您可以構(gòu)成放大。
它解決了什么問題,最常使用的地方是什么?
這就好比問“單例模式能解決什么問題?”,但我會(huì)給它一個(gè)機(jī)會(huì)。
Monad通常用于解決以下問題:
我需要為此類型創(chuàng)建新功能,并且仍然要組合此類型上的舊功能以使用新功能。
我需要捕獲一堆關(guān)于類型的操作,并將這些操作表示為可組合的對(duì)象,構(gòu)建越來越大的合成,直到我正確地表示了一系列操作,然后才需要從事情中得到結(jié)果。
我需要用一種討厭副作用的語言清晰地表示副作用操作
C#在其設(shè)計(jì)中使用了monad。如前所述,可為空的模式與“也許是單子”非常相似。LINQ完全由monad組成。該SelectMany方法是操作組合的語義工作。(Erik Meijer喜歡指出,每個(gè)LINQ函數(shù)實(shí)際上都可以由實(shí)現(xiàn)SelectMany;其他一切只是為了方便。)
為了闡明我正在尋找的理解類型,假設(shè)您正在將具有monad的FP應(yīng)用程序轉(zhuǎn)換為OOP應(yīng)用程序。您將如何將Monad的責(zé)任移植到OOP應(yīng)用程序中?
大多數(shù)OOP語言沒有足夠豐富的類型系統(tǒng)來直接表示monad模式本身。您需要一個(gè)類型系統(tǒng),該系統(tǒng)支持比通用類型更高類型的類型。所以我不會(huì)嘗試這樣做。相反,我將實(shí)現(xiàn)代表每個(gè)monad的泛型類型,并實(shí)現(xiàn)代表您需要的三個(gè)操作的方法:將值轉(zhuǎn)換為放大的值,(也許)將放大的值轉(zhuǎn)換為一個(gè)值,并將未放大的值的函數(shù)轉(zhuǎn)換為放大值的函數(shù)。
一個(gè)很好的起點(diǎn)是我們?nèi)绾卧贑#中實(shí)現(xiàn)LINQ。研究SelectMany方法;這是理解序列monad如何在C#中工作的關(guān)鍵。這是一種非常簡(jiǎn)單的方法,但功能非常強(qiáng)大!
建議進(jìn)一步閱讀:
有關(guān)C#中monad的更深入和理論上合理的解釋,我強(qiáng)烈建議我(Eric Lippert的同事)Wes Dyer關(guān)于該主題的文章。本文是monads最終為我“點(diǎn)擊”時(shí)向我解釋的內(nèi)容。
Monads的奇跡
一個(gè)很好的說明,為什么您可能想要一個(gè)monad (在示例中使用Haskell)。
您可能已經(jīng)發(fā)明了Monad?。ㄒ苍S你已經(jīng)擁有了。)丹·皮波尼
上一篇文章到JavaScript的“翻譯”。
James Coglan 所讀過的有關(guān)Monad 最佳入門的精選部分的從Haskell到JavaScript的翻譯

TA貢獻(xiàn)1827條經(jīng)驗(yàn) 獲得超8個(gè)贊
為什么我們需要單子?
我們只想使用函數(shù)進(jìn)行編程。(畢竟-FP是“功能性編程”)。
然后,我們有第一個(gè)大問題。這是一個(gè)程序:
f(x) = 2 * x
g(x,y) = x / y
我們?nèi)绾握f 要先執(zhí)行什么?我們?nèi)绾蝺H使用函數(shù)就可以形成一個(gè)有序的函數(shù)序列(即程序)?
解決方案:編寫函數(shù)。如果先要g然后f再寫就可以f(g(x,y))。好的但是 ...
更多問題:某些功能可能會(huì)失?。磄(2,0),除以0)。我們?cè)贔P沒有“例外”。我們?cè)撊绾谓鉀Q?
解決方案:讓函數(shù)讓函數(shù)返回兩種東西:g : Real,Real -> Real讓我們g : Real,Real -> Real | Nothing(函數(shù)從兩個(gè)實(shí)數(shù)轉(zhuǎn)換為(實(shí)數(shù)或虛無))而不是讓(函數(shù)從兩個(gè)實(shí)數(shù)轉(zhuǎn)換為實(shí)數(shù))。
但是函數(shù)(為了簡(jiǎn)化)應(yīng)該只返回一件事。
解決方案:讓我們創(chuàng)建一種新的要返回的數(shù)據(jù)類型,即“ 裝箱類型 ”,其中可能包含實(shí)數(shù)或僅是空值。因此,我們可以擁有g(shù) : Real,Real -> Maybe Real。好的但是 ...
現(xiàn)在會(huì)發(fā)生什么f(g(x,y))?f還沒準(zhǔn)備好消費(fèi)Maybe Real。而且,我們不想更改可以連接的每個(gè)函數(shù)g來消耗Maybe Real。
解決方案:讓我們有一個(gè)特殊的功能來“連接” /“組合” /“鏈接”功能。這樣,我們可以在幕后調(diào)整一項(xiàng)功能的輸出以提供下一項(xiàng)功能。
在我們的例子中:( g >>= f連接/組成g到f)。我們要>>=獲取g的輸出,對(duì)其進(jìn)行檢查,以防萬一它Nothing不調(diào)用f并返回Nothing;或者相反,提取盒裝Real并f用它喂食。(此算法只是>>=針對(duì)該Maybe類型的實(shí)現(xiàn))。
使用此相同的模式可以解決許多其他問題:1.使用“框”來整理/存儲(chǔ)不同的含義/值,并具有g(shù)返回這些“框值”的函數(shù)。2.讓作曲者/鏈接g >>= f者幫助將g的輸出連接到f的輸入,因此我們完全不需要更改f。
使用此技術(shù)可以解決的顯著問題是:
具有全局狀態(tài),函數(shù)序列中的每個(gè)函數(shù)(“程序”)可以共享:solution StateMonad。
我們不喜歡“不純函數(shù)”:對(duì)于相同輸入產(chǎn)生不同輸出的函數(shù)。因此,讓我們標(biāo)記這些函數(shù),使它們返回標(biāo)記/裝箱的值:monad。IO
完全幸福!

TA貢獻(xiàn)1811條經(jīng)驗(yàn) 獲得超6個(gè)贊
我會(huì)說與單子最接近的OO類比是“ 命令模式 ”。
在命令模式中,將普通語句或表達(dá)式包裝在命令對(duì)象中。該命令對(duì)象公開了一個(gè)執(zhí)行方法,該方法執(zhí)行包裝的語句。因此,語句變成了可以隨意傳遞和執(zhí)行的一流對(duì)象??梢越M合命令,因此您可以通過鏈接和嵌套命令對(duì)象來創(chuàng)建程序?qū)ο蟆?/p>
這些命令由單獨(dú)的對(duì)象(調(diào)用程序)執(zhí)行。使用命令模式(而不是僅執(zhí)行一系列普通語句)的好處是,不同的調(diào)用者可以對(duì)應(yīng)如何執(zhí)行命令應(yīng)用不同的邏輯。
命令模式可用于添加(或刪除)宿主語言不支持的語言功能。例如,在無例外的假設(shè)OO語言中,可以通過向命令公開“ try”和“ throw”方法來添加例外語義。當(dāng)命令調(diào)用throw時(shí),調(diào)用者將在命令列表(或樹)中回溯,直到最后一次“ try”調(diào)用為止。相反,您可以通過捕獲每個(gè)命令拋出的所有異常并將其轉(zhuǎn)換為錯(cuò)誤代碼,然后將其傳遞給下一個(gè)命令,從某種語言中刪除異常語義(如果您認(rèn)為異常不好)。
像這樣的更加花哨的執(zhí)行語義,例如事務(wù),非確定性執(zhí)行或連續(xù)性,都可以用本機(jī)不支持的語言來實(shí)現(xiàn)。如果您考慮一下,這是一個(gè)非常強(qiáng)大的模式。
現(xiàn)在,實(shí)際上,命令模式并未像這樣被用作通用語言功能。將每個(gè)語句轉(zhuǎn)換為單獨(dú)的類的開銷將導(dǎo)致大量的樣板代碼。但是從原理上講,它可以用來解決與單子在fp中解決相同的問題。
添加回答
舉報(bào)