摘要:不論你想要成熟的面向?qū)ο蟮某绦蛟O(shè)計,還是程序式或函數(shù)式編程,都可以做到。但我們不禁要問,擅長函數(shù)式編程嗎本文系國內(nèi)管理平臺工程師編譯整理。在函數(shù)式編程中,目標(biāo)之一是減輕副作用。
許多通用程序設(shè)計語言試圖兼容大多數(shù)編程范式,PHP 就屬于其中之一。不論你想要成熟的面向?qū)ο蟮某绦蛟O(shè)計,還是程序式或函數(shù)式編程,PHP 都可以做到。但我們不禁要問,PHP 擅長函數(shù)式編程嗎?本文系國內(nèi) ITOM 管理平臺 OneAPM 工程師編譯整理。
筆者在今年冬天開始時,在 Recurse Center致力于學(xué)習(xí) Clojure,更加深入地了解了函數(shù)式編程,并重新拾起 PHP 的客戶端工作。但筆者仍然希望運用一些高階函數(shù)和概念,并對它們進行研究。
筆者已經(jīng)在 PHP 中實施了模擬 LISP 語言,并看到了一些在 PHP 中通過使用 underscore 類庫以兼容某些關(guān)鍵函數(shù)方法的嘗試。但為了使 Clojure 在寫入其它編程語言時仍然保有較高的速度,筆者特意鏡像 Clojure 的標(biāo)準(zhǔn)庫,以使自己能在編寫真正的 PHP 代碼時,以 Clojure 的方式思考。雖然在學(xué)習(xí)的過程中繞了一些彎路,筆者仍然愿意向各位展示自己是如何實現(xiàn) interleave 函數(shù)的。
幸運地是,已經(jīng)有人執(zhí)行了 array_some 和 array_every,并且非常地道(至少筆者這么認(rèn)為)。
/** * Returns true if the given predicate is true for all elements. * credit: array_every and array_some.php * https://gist.github.com/kid-icarus/8661319 */ function every(callable $callback, array $arr) { foreach ($arr as $element) { if (!$callback($element)) { return FALSE; } } return TRUE; } /** * Returns true if the given predicate is true for at least one element. * credit: array_every and array_some.php * https://gist.github.com/kid-icarus/8661319 */ function some(callable $callback, array $arr) { foreach ($arr as $element) { if ($callback($element)) { return TRUE; } } return FALSE; }
我們只要簡單地取消調(diào)用 every 函數(shù),就可以運用 not-every 函數(shù)插入一些容易實現(xiàn)的目標(biāo),同時仍然有相同 signature。
/** * Returns true if the given predicate is not true for all elements. */ function not_every(callable $callback, array $arr) { return !every($callable, $arr); }
如你所見,筆者已經(jīng)去掉了前綴 array_。PHP 的不便之處在于強調(diào)序函數(shù),通常使用前綴 array_ 來運行數(shù)列。筆者將此理解為這兩種函數(shù)的作者是在相互模仿。雖然數(shù)列在 PHP 中已經(jīng)形成事實數(shù)據(jù)結(jié)構(gòu),但標(biāo)準(zhǔn)數(shù)據(jù)庫以此種方式被寫入并不常見。
這一標(biāo)準(zhǔn)適用于基本高階函數(shù),你可以使用 array_map、array_reduce和 array_filter 結(jié)尾,而不是 map,recude 和 filter。如果這些還不夠,那參數(shù)便不一致了。array_reduce 和 array_filter 都以數(shù)列為第一個參數(shù),然后以回調(diào)值作為第二個參數(shù),首先調(diào)回 array_map。在 Clojure 中,通常首先運行回調(diào)函數(shù),所以讓我們將這些函數(shù)重新命名,然后只需一步就能使這些簽名變得正常:
/** * Applies callable to each item in array, return new array. */ function map(callable $callback, array $arr) { return array_map($callback, $arr); } /** * Return a new array with elements for which predicate returns true. */ function filter(callable $callback, array $arr, $flag=0) { return array_filter($arr, $callback, $flag); } /** * Iteratively reduce the array to a single value using a callback function */ function reduce(callable $callback, array $arr, $initial=NULL) { return array_reduce($arr, $callback, $initial); }
我們目前沒有其它方法,所以當(dāng) Clojure 中的 reduce 函數(shù)通過了初始值并作為第二個參數(shù)時,它便有了另一個簽名。鑒于此,我們從現(xiàn)在開始就將 initial 作為最終值——畢竟相對于原函數(shù)來說,這仍然是一大進步。另外,我們也將在過濾函數(shù)中保留 $flag,它決定了是否全部通過鍵和值,還是只通過鍵。
在 Clojure 中,first 和 last 是十分有用的兩個函數(shù),相當(dāng)于 PHP 中的 array_shift 和 array_pop。它們的關(guān)鍵不同之處在于:PHP 中兩個命令具有毀壞性。以 array_shift 為例,它返回數(shù)列的第一項,同時又從原始數(shù)列中移除該項(當(dāng)數(shù)列被引用通過時)。在函數(shù)式編程中,目標(biāo)之一是減輕副作用。所以在后臺用 first 和 last 函數(shù)將數(shù)列復(fù)制一份,這樣原始數(shù)列就永遠不會被更改了。與之相對應(yīng)的是 rest 和 but-last 函數(shù),我們可以繼續(xù)使用 array_slice 來返回該部分。
/** * Returns the first item in an array. */ function first(array $arr) { $copy = array_slice($arr, 0, 1, true); return array_shift($copy); } /** * Returns the last item in an array. */ function last(array $arr) { $copy = array_slice($arr, 0, NULL, true); return array_pop($copy); } /** * Returns all but the first item in an array. */ function rest(array $arr) { return array_slice($arr, 1, NULL, true); } /** * Returns all but the last item in an array. */ function but_last(array $arr) { return array_slice($arr, 0, -1, true); }
當(dāng)然,這些都只是低階函數(shù),可能看起來并不那么讓人興奮,但它們遲早會有用。順便問一下,大家知道 PHP 中與這些函數(shù)相對應(yīng)的「應(yīng)用」( https://en.wikipedia.org/wiki/Apply)嗎?答案可能是否定的。因為它們的名字十分深奧,不像其它編程語言中那些概念相同但名稱普通的命令。讓我們繼續(xù)將 call_user_func_array 替換為 apply 函數(shù)吧。
/** * Alias call_user_func_array to apply. */ function apply(callable $callback, array $args) { return call_user_func_array($callback, $args); }
這太讓人興奮了!當(dāng)我們將函數(shù)名稱變得地道,并創(chuàng)建出低級別的抽象名稱,便有了一個能幫助我們創(chuàng)建更多有趣名稱的平臺。讓我們用 apply 幫助我們創(chuàng)建 complement:
function complement(callable $f) { return function() use ($f) { $args = func_get_args(); return !apply($f, $args); }; }
這里使用了 func_get_args()函數(shù),當(dāng)所有值通過原始函數(shù)時,它就能夠抓取一個數(shù)列,這一數(shù)列中所有的值都按照它們通過時的順序排列。我們繼續(xù)返回匿名函數(shù),該函數(shù)能通過 use 獲取原始函數(shù) $f(因為所有的函數(shù)在PHP中都有新的域),然后在 $args 中調(diào)用 apply。
太好了,現(xiàn)在我們有了 complement 函數(shù),它能讓我們更加容易地實施與filter 函數(shù)相反的 remove 函數(shù)。通過返回回調(diào)的 complement 傳遞給filter,當(dāng)所有數(shù)據(jù)與預(yù)設(shè)條件不相符時,返回所有數(shù)據(jù)。
/** * Return a new array with elements for which predicate returns false. */ function remove(callable $callback, array $arr, $flag=0) { return filter(complement($callback), $arr, $flag); }
換個角度來說,array_merge 和 contact 是等效的。下面以 Cons 和 conj 為例,在 Clojure 中,它們是向集合的開始或末尾增加項的標(biāo)準(zhǔn)方式,
/** * Alias array_merge to concat. */ function concat() { $arrs = func_get_args(); return apply("array_merge", $arrs); } /** * cons(truct) * Returns a new array where x is the first element and $arr is the rest. */ function cons($x, array $arr) { return concat(array($x), $arr); } /** * conj(oin) * Returns a new arr with the xs added. * @param $arr * @param & xs add"l args to be added to $arr. */ function conj() { $args = func_get_args(); $arr = first($args); return concat($arr, rest($args)); }
例如,現(xiàn)在調(diào)用這兩個函數(shù),會生成相同的結(jié)果:
cons(1, array(2, 3, 4)); conj(array(1), 2, 3, 4);
這些低階工具足以讓 interleave 的書寫變得十分簡單。首先,我們使用func_get_args,取代在函數(shù)簽名中使用聲明參數(shù),這樣便能采用大量的數(shù)列作為函數(shù)參數(shù)。然后,我們將每個數(shù)列的第一項提出來組成一個新的數(shù)列,余下的每個數(shù)列作為每一個新數(shù)列。接著,檢查每個數(shù)列是否都保留有元素,再使用 concat 函數(shù)連接交錯數(shù)列的結(jié)果,如此反復(fù)。以可讀的實施以及與 Clojure 版本幾乎無差別的函數(shù)結(jié)果為結(jié)束,得到的結(jié)果就是證明 Clojure 生成了惰性序列。
/** * Returns a sequence of the first item in each collection then the second, etc. */ function interleave() { $arrs = func_get_args(); $firsts = map("first", $arrs); $rests = map("rest", $arrs); if (every(function($a) { return !empty($a); }, $rests)) { return concat($firsts, apply("interleave", $rests)); } return $firsts; }
因此,當(dāng)我們調(diào)用長度可變的數(shù)列來制作函數(shù)時:
interleave([1, 2, 3, 4], ["a", "b", "c", "d", "e"], ["w", "x", "y", "z"])
插入所有三個數(shù)列減去多余項,以其作為結(jié)果數(shù)列并以此結(jié)尾:
array ( 0 => 1, 1 => "a", 2 => "w", 3 => 2, 4 => "b", 5 => "x", 6 => 3, 7 => "c", 8 => "y", 9 => 4, 10 => "d", 11 => "z", )
當(dāng)然,Clojure 有非常棒的功能性,在這里我們并沒有提到,例如 interleave,它是返回惰性序列,而不是靜態(tài)采集。此外,由于數(shù)列會像 PHP 中的映射一樣加倍,那些類似于 assoc 的模擬方法就變得模棱兩可。如果大家對這些感興趣,并且想在下一個項目中使用它們,這些代碼已放到 GitHub 上供您閱讀參考。
cljphp on GitHub
原文地址:http://blackwood.io/porting-clojure-php-better-functional-programming/
本文系 OneAPM 工程師編譯整理。OneAPM 是應(yīng)用性能管理領(lǐng)域的新興領(lǐng)軍企業(yè),能幫助企業(yè)用戶和開發(fā)者輕松實現(xiàn):緩慢的程序代碼和 SQL 語句的實時抓取。想閱讀更多技術(shù)文章,請訪問 OneAPM 官方博客。
本文轉(zhuǎn)自 OneAPM 官方博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/21375.html
摘要:我們的目標(biāo)是建立對每一種語言的認(rèn)識,它們是如何進化的,未來將走向何方。有點的味道是堅持使用動態(tài)類型,但唯一還收到合理擁泵的編程語言,然而一些在企業(yè)的大型團隊中工作的開發(fā)者擇認(rèn)為這會是的一個缺陷。 為什么我們需要如此多的JVM語言? 在2013年你可以有50中JVM語言的選擇來用于你的下一個項目。盡管你可以說出一大打的名字,你會準(zhǔn)備為你的下一個項目選擇一種新的JVM語言么? 如今借助來自...
摘要:第一節(jié)函數(shù)式范式什么是函數(shù)式編程函數(shù)式編程英語或稱函數(shù)程序設(shè)計,又稱泛函編程,是一種編程范型,它將電腦運算視為數(shù)學(xué)上的函數(shù)計算,并且避免使用程序狀態(tài)以及易變對象。 第一節(jié) 函數(shù)式范式 1. 什么是函數(shù)式編程 函數(shù)式編程(英語:functional programming)或稱函數(shù)程序設(shè)計,又稱泛函編程,是一種編程范型,它將電腦運算視為數(shù)學(xué)上的函數(shù)計算,并且避免使用程序狀態(tài)以及易變對...
摘要:根據(jù)對社區(qū)和新特性的深刻理解,他創(chuàng)作了函數(shù)式編程一書。問你在倫敦社區(qū)的經(jīng)歷是否幫助你創(chuàng)作了函數(shù)式編程這本書絕對是這樣。我認(rèn)為引入函數(shù)式編程會為很多編程任務(wù)提供方便。問之前的是面向?qū)ο蟮?,現(xiàn)在全面支持函數(shù)式編程。 非商業(yè)轉(zhuǎn)載請注明作譯者、出處,并保留本文的原始鏈接:http://www.ituring.com.cn/article/199271 Richard Warburto...
閱讀 875·2021-09-26 09:55
閱讀 2154·2021-09-22 15:44
閱讀 1555·2019-08-30 15:54
閱讀 1400·2019-08-30 15:54
閱讀 2744·2019-08-29 16:57
閱讀 582·2019-08-29 16:26
閱讀 2562·2019-08-29 15:38
閱讀 2208·2019-08-26 11:48