十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
本篇內(nèi)容主要講解“Spark局部套用和部分應(yīng)用方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Spark局部套用和部分應(yīng)用方法是什么”吧!
創(chuàng)新互聯(lián)公司是一家專業(yè)提供雞西梨樹企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站、成都外貿(mào)網(wǎng)站建設(shè)公司、H5場(chǎng)景定制、小程序制作等業(yè)務(wù)。10年已為雞西梨樹眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)的建站公司優(yōu)惠進(jìn)行中。
局部套用 和部分應(yīng)用 是來源于數(shù)學(xué)的語言技術(shù)(基于 20 世紀(jì)數(shù)學(xué)家 Haskell Curry 和其他人的工作成果)。這兩種技術(shù)存在于各種類型的語言中,可以單獨(dú)或同時(shí)存在于函數(shù)式語言中。局部套用和部分應(yīng)用使您能夠處理函數(shù)或方法的參數(shù)數(shù)量,通常的方法是為一些參數(shù)提供一個(gè)或多個(gè)默認(rèn)值(稱為修正 參數(shù))。所有 Java 下一代語言都包括局部套用和部分應(yīng)用,但以不同的方式實(shí)現(xiàn)它們。在本文中,我將介紹這兩種技術(shù)的不同之處,并展示它們?cè)?Scala、Groovy 和 Clojure 中的實(shí)現(xiàn)細(xì)節(jié),以及實(shí)際應(yīng)用。
出于本部分的目的,方法(method) 和 函數(shù)(function) 是可互換的。支持局部套用和部分應(yīng)用的面向?qū)ο笳Z言使用方法。同樣,函數(shù)參數(shù)(function parameter) 和 函數(shù)參數(shù)(function argument) 也是可互換的。由于這些概念起源于數(shù)學(xué),因此我自始至終使用的是 函數(shù)(function) 和 參數(shù)(argument),但這并不意味著這兩種技術(shù)對(duì)方法不起作用。
對(duì)于業(yè)余人士來說,局部套用和部分應(yīng)用具有相同的效果。使用這兩種技術(shù)時(shí),都可以創(chuàng)建一個(gè)一些參數(shù)具有預(yù)先提供值的函數(shù)版本:
局部套用是將多參數(shù)函數(shù)轉(zhuǎn)換為一系列單參數(shù)函數(shù)。它描述了轉(zhuǎn)換過程,而不是轉(zhuǎn)換函數(shù)的調(diào)用。調(diào)用方可以確定應(yīng)用了多少參數(shù),從而創(chuàng)建一個(gè)參數(shù)更少的導(dǎo)出函數(shù)。
部分應(yīng)用將多參數(shù)函數(shù)轉(zhuǎn)換為一個(gè)參數(shù)更少的多參數(shù)函數(shù),其值為提前提供的省略參數(shù)的值。本技術(shù)的名稱非常恰當(dāng):它將一些參數(shù)部分應(yīng)用到函數(shù),并返回一個(gè)具有簽名(由剩余參數(shù)組成)的函數(shù)。
使用局部套用和部分應(yīng)用,可以提供參數(shù)值并返回一個(gè)可使用缺少參數(shù)調(diào)用的函數(shù)。但是,對(duì)函數(shù)應(yīng)用局部套用會(huì)返回鏈中的下一個(gè)函數(shù),而部分應(yīng)用會(huì)將參數(shù)值綁到在運(yùn)算期間提供的值上,生成一個(gè)具有更少 元數(shù)(參數(shù)的數(shù)量)的函數(shù)。當(dāng)考慮具有兩個(gè)以上元數(shù)的函數(shù)時(shí),這一區(qū)別會(huì)更加明顯。例如,process(x, y, z)
函數(shù)的完全套用版本是process(x)(y)(z)
,其中 process(x)
和 process(x)(y)
都是接受一個(gè)參數(shù)的函數(shù)。如果只對(duì)第一個(gè)參數(shù)應(yīng)用了局部套用,那么 process(x)
的返回值將是接受一個(gè)參數(shù)的函數(shù),因此僅接受一個(gè)參數(shù)。與此相反,在使用部分應(yīng)用時(shí),會(huì)剩下一個(gè)具有更少元數(shù)的函數(shù)。對(duì) process(x, y, z)
的一個(gè)參數(shù)使用部分應(yīng)用會(huì)生成接受兩個(gè)參數(shù)的函數(shù):process(y, z)
。
這兩種技術(shù)的結(jié)果通常是相同的,但二者的區(qū)別也很重要,人們通常會(huì)對(duì)它們之間的區(qū)別產(chǎn)生誤解。更復(fù)雜的是,Groovy 可以實(shí)現(xiàn)部分應(yīng)用和局部套用,但都將它們稱為 currying
。而 Scala 具有偏應(yīng)用函數(shù)(partially applied function)和 PartialFunction
,盡管它們的名稱類似,但它們卻是兩個(gè)不同的概念。
Scala 支持局部套用和部分應(yīng)用,還支持特征(trait),特征可以定義約束函數(shù)(constrained function)。
在 Scala 中,函數(shù)可以將多個(gè)參數(shù)列表定義為括號(hào)組。調(diào)用參數(shù)數(shù)量比其定義數(shù)量少的函數(shù)時(shí),會(huì)返回一個(gè)將缺少參數(shù)列表作為其參數(shù)的函數(shù)。請(qǐng)考慮 Scala 文檔的示例,如清單 1 所示。
def filter(xs: List[Int], p: Int => Boolean): List[Int] = if (xs.isEmpty) xs else if (p(xs.head)) xs.head :: filter(xs.tail, p) else filter(xs.tail, p) def modN(n: Int)(x: Int) = ((x % n) == 0) val nums = List(1, 2, 3, 4, 5, 6, 7, 8) println(filter(nums, modN(2))) println(filter(nums, modN(3)))
在清單 1 中,filter()
函數(shù)遞歸地應(yīng)用傳遞的過濾條件。modN()
函數(shù)定義了兩個(gè)參數(shù)列表。在我使用 filter()
調(diào)用 modN
時(shí),我傳遞了一個(gè)參數(shù)。filter()
函數(shù)被作為函數(shù)的第二個(gè)參數(shù),具有一個(gè) Int
參數(shù)和一個(gè) Boolean
返回值,這與我傳遞的局部套用函數(shù)的簽名相匹配。
在 Scala 中還可以部分應(yīng)用函數(shù),如清單 2 所示。
def price(product : String) : Double = product match { case "apples" => 140 case "oranges" => 223 } def withTax(cost: Double, state: String) : Double = state match { case "NY" => cost * 2 case "FL" => cost * 3 }val locallyTaxed = withTax(_: Double, "NY")val costOfApples = locallyTaxed(price("apples")) assert(Math.round(costOfApples) == 280)
在清單 2 中,我首先創(chuàng)建了一個(gè) price
函數(shù),它返回了產(chǎn)品和價(jià)格之間的映射。然后我創(chuàng)建了一個(gè) withTax()
函數(shù),其參數(shù)為 cost
和state
。但是,在特殊的源文件中,我知道要專門處理一個(gè)國(guó)家的稅收。我沒有對(duì)每次調(diào)用的額外參數(shù)應(yīng)用局部套用,而是部分應(yīng)用了 state
參數(shù),并返回一個(gè) state 值固定的函數(shù)。locallyTaxed
函數(shù)接受一個(gè)參數(shù),即 cost
。
Scala PartialFunction
特征可以與模式無縫地配合使用(請(qǐng)閱讀函數(shù)式思維 系列的 "Either 樹和模式匹配" 部分中的模式匹配)。盡管名稱類似,但此特征不會(huì)創(chuàng)建偏應(yīng)用函數(shù)。相反,可以使用它定義僅適用于值和類型定義子集的函數(shù)。
Case 塊是應(yīng)用偏函數(shù)(partial function)的一種方式。清單 3 使用了 Scala 的 case
,沒有傳統(tǒng)對(duì)應(yīng)的 match
操作符。
case
val cities = Map("Atlanta" -> "GA", "New York" -> "New York", "Chicago" -> "IL", "San Francsico " -> "CA", "Dallas" -> "TX") cities map { case (k, v) => println(k + " -> " + v) }
在清單 3 中,我創(chuàng)建了一個(gè)城市和該城市所對(duì)應(yīng)的州的映射。然后,我對(duì)該集合調(diào)用了 map
函數(shù),map
會(huì)拆開鍵值對(duì)以輸出它們。在 Scala 中,包含 case
聲明的代碼塊是定義匿名函數(shù)的一種方式。不使用 case
可以更簡(jiǎn)潔地定義匿名函數(shù),但是,case
語法提供了如清單 4 所示的額外好處。
map
和 collect
之間的區(qū)別List(1, 3, 5, "seven") map { case i: Int ? i + 1 } // won't work // scala.MatchError: seven (of class java.lang.String) List(1, 3, 5, "seven") collect { case i: Int ? i + 1 } // verify assert(List(2, 4, 6) == (List(1, 3, 5, "seven") collect { case i: Int ? i + 1 }))
在清單 4 中,我不能在具有 case
的異構(gòu)集合上使用 map
:我收到了 MatchError
,因?yàn)楹瘮?shù)試圖增加 seven
字符串。但是 collect
工作正常。為什么會(huì)出現(xiàn)這種不同?什么地方出錯(cuò)了?
Case 塊定義的是偏函數(shù),而不是偏應(yīng)用函數(shù)。偏函數(shù) 具有有限的允許值。例如,數(shù)學(xué)函數(shù) 1/x
是無效的,如果 x = 0
。偏函數(shù)提供了一種定義允許值約束的方式。在 清單 4 的 collect
示例中,定義了 Int
而不是 String
的約束,因此沒有收集 seven
字符串。
要定義偏函數(shù),還可以使用 PartialFunction
特征,如清單 5 所示。
val answerUnits = new PartialFunction[Int, Int] { def apply(d: Int) = 42 / d def isDefinedAt(d: Int) = d != 0 } assert(answerUnits.isDefinedAt(42)) assert(! answerUnits.isDefinedAt(0)) assert(answerUnits(42) == 1) //answerUnits(0) //java.lang.ArithmeticException: / by zero
在清單 5 中,我從 PartialFunction
特征導(dǎo)出了 answerUnits
,并提供了兩個(gè)函數(shù):apply()
和 isDefinedAt()
。apply()
函數(shù)計(jì)算值。我使用了 isDefinedAt()
(PartialFunction
的必要方法)來定義確定參數(shù)適用性的約束。
還可以使用 case
塊實(shí)現(xiàn)偏函數(shù),清單 5 的answerUnits
可以采用更簡(jiǎn)潔的方式編寫,如清單 6 所示。
answerUnits
的另一種定義def pAnswerUnits: PartialFunction[Int, Int] = { case d: Int if d != 0 => 42 / d } assert(pAnswerUnits(42) == 1) //pAnswerUnits(0) //scala.MatchError: 0 (of class java.lang.Integer)
在清單 6 中,我結(jié)合使用了 case
和保衛(wèi)條件來約束值并同時(shí)提供值。與 清單 5 的一個(gè)明顯區(qū)別是 MatchError
(而不是ArithmeticException
),因?yàn)榍鍐?6 使用了模式匹配。
偏函數(shù)并不僅局限于數(shù)值類型。它可以使用所有類型的數(shù)值,包括 Any
。可以考慮增量器(incrementer)的實(shí)現(xiàn),如清單 7 所示。
def inc: PartialFunction[Any, Int] = { case i: Int => i + 1 } assert(inc(41) == 42) //inc("Forty-one") //scala.MatchError: Forty-one (of class java.lang.String) assert(inc.isDefinedAt(41)) assert(! inc.isDefinedAt("Forty-one")) assert(List(42) == (List(41, "cat") collect inc))
在清單 7 中,我定義了一個(gè)偏函數(shù)來接受任意類型的輸入 (Any
),但選擇對(duì)類型子集做出反應(yīng)。請(qǐng)注意,我還可以調(diào)用偏函數(shù)的isDefinedAt()
函數(shù)。使用 case
的 PartialFunction
特征的實(shí)現(xiàn)者可以調(diào)用 isDefinedAt()
,它是隱式定義的。在 清單 4 中,我說明了 map
和 collect
的表現(xiàn)不同。偏函數(shù)的行為解釋了它們的區(qū)別:collect
旨在接受偏函數(shù),并調(diào)用元素的 isDefinedAt()
函數(shù),會(huì)忽略那些不匹配的函數(shù)。
在 Scala 中,偏函數(shù)和偏應(yīng)用函數(shù)的名稱類似,但是它們提供了不同的正交特性集。例如,沒有什么可以阻止您部分地應(yīng)用偏函數(shù)。
在我的函數(shù)式思維 系列的 "運(yùn)用函數(shù)式思維,第 3 部分" 中詳細(xì)介紹了 Groovy 中的局部套用和部分應(yīng)用。Groovy 通過 curry()
函數(shù)實(shí)現(xiàn)了局部套用,該函數(shù)來自 Closure
類。盡管名稱如此,但 curry()
實(shí)際上通過處理其下面的閉包塊來實(shí)現(xiàn)部分應(yīng)用。但是,您可以模擬局部套用,方法是使用部分應(yīng)用將函數(shù)減少為一系列部分應(yīng)用的單參數(shù)函數(shù),如清單 8 所示。
def volume = { h, w, l -> return h * w * l } def area = volume.curry(1) def lengthPA = volume.curry(1, 1) //partial application def lengthC = volume.curry(1).curry(1) // currying println "The volume of the 2x3x4 rectangular solid is ${volume(2, 3, 4)}" println "The area of the 3x4 rectangle is ${area(3, 4)}" println "The length of the 6 line is ${lengthPA(6)}" println "The length of the 6 line via curried function is ${lengthC(6)}"
在清單 8 中,在兩種 length
情況下,我使用 curry()
函數(shù)部分應(yīng)用了參數(shù)。但是,在使用 lengthC
時(shí),通過部分地應(yīng)用參數(shù),直到出現(xiàn)一連串的單參數(shù)函數(shù)為止,我制造了一種使用局部套用的幻覺。
Clojure 包含 (partial f a1 a2 ...)
函數(shù),它具有函數(shù) f
以及比所需數(shù)量更少的參數(shù),而且返回一個(gè)在提供剩余參數(shù)時(shí)調(diào)用的部分應(yīng)用函數(shù)。清單 9 顯示了兩個(gè)示例。
(def subtract-from-hundred (partial - 100)) (subtract-from-hundred 10) ; same as (- 100 10) ; 90 (subtract-from-hundred 10 20) ; same as (- 100 10 20) ; 70
在清單 9 中,我將 subtract-from-hundred
函數(shù)定義為部分應(yīng)用的 -
運(yùn)算符(Clojure 中的運(yùn)算符與函數(shù)無法區(qū)分),并提供 100 作為部分應(yīng)用的參數(shù)。Clojure 中的部分應(yīng)用適用于單參數(shù)函數(shù)和多參數(shù)函數(shù),如清單 9 中的兩個(gè)示例所示。
由于 Clojure 是動(dòng)態(tài)類型的,并且支持可變參數(shù)列表,因此局部套用并不能作為一種語言功能來實(shí)現(xiàn)。部分應(yīng)用將會(huì)處理必要的情況。但是,Clojure 被添加到 reducers 庫(參見 參考資料)的命名空間私有 (defcurried ...)
函數(shù),支持在該庫中更輕松地定義一些函數(shù)。鑒于 Clojure 的 Lisp 傳承的靈活特點(diǎn),可以輕松擴(kuò)大 (defcurried ...)
的使用范圍。
盡管局部套用和部分應(yīng)用具有復(fù)雜的定義和大量實(shí)現(xiàn)細(xì)節(jié),但是它們?cè)趯?shí)際編程中都占有一席之地。
局部套用(和部分應(yīng)用)適合在傳統(tǒng)的面向?qū)ο笳Z言中實(shí)現(xiàn)工廠函數(shù)的位置使用。作為一個(gè)示例,清單 10 在 Groovy 中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的adder
函數(shù)。
def adder = { x, y -> x + y} def incrementer = adder.curry(1) println "increment 7: ${incrementer(7)}" // 8
在清單 10 中,我使用 adder()
函數(shù)來導(dǎo)出 incrementer
函數(shù)。同樣,在 清單 2 中,我使用部分應(yīng)用創(chuàng)建了一個(gè)更簡(jiǎn)潔的本地函數(shù)版本。
Gang of Four 設(shè)計(jì)模式之一是 Template Method 模式。它的用途是幫助定義算法 shell,使用內(nèi)部抽象方法來實(shí)現(xiàn)稍后的實(shí)現(xiàn)靈活性。部分應(yīng)用和局部套用可以解決相同的問題。使用部分應(yīng)用提供已知行為,并讓其他參數(shù)免費(fèi)用于實(shí)現(xiàn)細(xì)節(jié),這模擬了此面向?qū)ο笤O(shè)計(jì)模式的實(shí)現(xiàn)。
與 清單 2 類似,一種常見的情況是您有一系列使用相似參數(shù)值調(diào)用的函數(shù)。例如,當(dāng)與持久性框架交互時(shí),必須將數(shù)據(jù)源作為第一個(gè)參數(shù)進(jìn)行傳遞。通過使用部分應(yīng)用,可以隱式地提供值,如清單 11 所示。
(defn db-connect [data-source query params] ...) (def dbc (partial db-connect "db/some-data-source")) (dbc "select * from %1" "cust")
在清單 11 中,我使用了便利的 dbc
函數(shù)來訪問數(shù)據(jù)函數(shù),無需提供數(shù)據(jù)源,就可以自動(dòng)提供數(shù)據(jù)源。面向?qū)ο缶幊痰木瑁[含 this
上下文似乎出現(xiàn)在所有函數(shù)中)可以通過使用局部套用為所有函數(shù)提供 this
來實(shí)現(xiàn),這使得它對(duì)用戶不可見。
到此,相信大家對(duì)“Spark局部套用和部分應(yīng)用方法是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!