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