摘要:聲明式編程一種編程范式,與命令式編程相對立。常見的聲明式編程語言有數據庫查詢語言,正則表達式邏輯編程函數式編程組態管理系統等。函數式編程,特別是純函數式編程,嘗試最小化狀態帶來的副作用,因此被認為是聲明式的。
編程范式與函數式編程 一、編程范式的分類
常見的編程范式有:函數式編程、程序編程、面向對象編程、指令式編程等。在面向對象編程的世界,程序是一系列相互作用(方法)的對象(Class Instances),而在函數式編程的世界,程序會是一個無狀態的函數組合序列。不同的編程語言也會提倡不同的“編程范型”。一些語言是專門為某個特定的范型設計的,如Smalltalk和Java支持面向對象編程。而Haskell和Scheme則支持函數式編程。現代編程語言的發展趨勢是支持多種范型,如 C#、Java 8+、Kotlin、 Scala、ES6+ 等等。
與成百種編程語言相比,編程范式要少得多。多數范式之間僅相差一個或幾個概念,比如函數編程范式,在加入了狀態(state)之后就變成了面向對象編程范式。
雖然范式有很多種,但是可以簡單分為三類:
計算機的硬件負責運行使用命令式的風格來寫的機器碼。計算機硬件的工作方式基本上都是命令式的。大部分的編程語言都是基于命令式的。高級語言通常都支持四種基本的語句:
(1)運算語句
一般來說都表現了在存儲器內的數據進行運算的行為,然后將結果存入存儲器中以便日后使用。高階命令式編程語言更能處理復雜的表達式,產生四則運算和函數計算的結合。
(2)循環語句
容許一些語句反復運行數次。循環可依據一個默認的數目來決定運行這些語句的次數;或反復運行它們,直至某些條件改變。
(3)條件分支
容許僅當某些條件成立時才運行某個區塊。否則,這個區塊中的語句會略去,然后按區塊后的語句繼續運行。
(4)無條件分支
容許運行順序轉移到程序的其他部分之中。包括跳躍(在很多語言中稱為Goto)、副程序和Procedure等。
循環、條件分支和無條件分支都是控制流程。
早期的命令式編程語言,例如匯編,都是機器指令。雖然硬件的運行更容易,卻阻礙了復雜程序的設計。
怎樣為一個模糊不清的問題找到一個最恰當的描述(問題描述)? 抽象(Abstraction)通常是我們用來簡化復雜的現實問題的方法。在面向對象程序編程里,計算機程序會被設計成彼此相關的對象。對象則指的是類的實例。它將對象作為程序的基本單元,將程序和數據封裝其中,以提高軟件的重用性、靈活性和擴展性,對象里的程序可以訪問及經常修改對象相關連的數據。對象包含數據(字段、屬性)與方法。
面向對象程序設計可以看作一種在程序中包含各種獨立而又互相調用的對象的思想,這與傳統的思想剛好相反:傳統的程序設計主張將程序看作一系列函數的集合,或者直接就是一系列對計算機下達的指令。面向對象程序設計中的每一個對象都應該能夠接受數據、處理數據并將數據傳達給其它對象,因此它們都可以被看作一個小型的“機器”,即對象。目前已經被證實的是,面向對象程序設計推廣了程序的靈活性和可維護性,并且在大型項目設計中廣為應用。此外,支持者聲稱面向對象程序設計要比以往的做法更加便于學習,因為它能夠讓人們更簡單地設計并維護程序,使得程序更加便于分析、設計、理解。反對者在某些領域對此予以否認。
當我們提到面向對象的時候,它不僅指一種程序設計方法。它更多意義上是一種程序開發方式。在這一方面,我們必須了解更多關于面向對象系統分析和面向對象設計(Object Oriented Design,簡稱OOD)方面的知識。許多流行的編程語言是面向對象的,它們的風格就是會透由對象來創出實例。重要的面向對象編程語言包含Common Lisp、Python、C++、Objective-C、Smalltalk、Delphi、Java、Swift、C#、Perl、Ruby 與 PHP等。面向對象編程中,通常利用繼承父類,以實現代碼重用和可擴展性。
3.聲明式編程(Declarative programming)一種編程范式,與命令式編程相對立。它描述目標的性質,讓計算機明白目標,而非具體過程。聲明式編程不用告訴計算機問題領域,從而避免隨之而來的副作用。而命令式編程則需要用算法來明確的指出每一步該怎么做。聲明式編程通常被看做是形式邏輯的理論,把計算看做推導。聲明式編程大幅簡化了并行計算的編寫難度。
常見的聲明式編程語言有:
數據庫查詢語言(SQL,XQuery)
正則表達式
邏輯編程
函數式編程
組態管理系統等。
聲明式編程透過函數、推論規則或項重寫(term-rewriting)規則,來描述變量之間的關系。它的語言運行器(編譯器或解釋器)采用了一個固定的算法,以從這些關系產生結果。很多文本標記語言例如HTML、MXML、XAML和XSLT往往是聲明式的。函數式編程,特別是純函數式編程,嘗試最小化狀態帶來的副作用,因此被認為是聲明式的。不過,大多數函數式編程語言,例如Scheme、Clojure、Haskell、OCaml、Standard ML和Unlambda,允許副作用的存在。
二、3種最常用編程范式的特點過程式編程的核心在于模塊化,在實現過程中使用了狀態,依賴了外部變量,導致很容易影響附近的代碼,可讀性較少,后期的維護成本也較高。
函數式編程的核心在于“避免副作用”,不改變也不依賴當前函數外的數據。結合不可變數據、函數是第一等公民等特性,使函數帶有自描述性,可讀性較高。
面向對象編程的核心在于抽象,提供清晰的對象邊界。結合封裝、集成、多態特性,降低了代碼的耦合度,提升了系統的可維護性。
不同的范式的出現,目的就是為了應對不同的場景,但最終的目標都是提高生產力。
1、過程式編程(Procedural)過程式編程和面向對象編程的區別并不在于是否使用函數或者類,也就是說用到類或對象的可能是過程式編程,只用函數而沒有類的也可能是面向對象編程。那么他們的區別又在哪兒呢?
面向過程其實是最為實際的一種思考方式,可以說面向過程是一種基礎的方法,它考慮的是實際地實現。一般的面向過程是從上往下步步求精,所以面向過程最重要的是模塊化的思想方法。當程序規模不是很大時,面向過程的方法還會體現出一種優勢。因為程序的流程很清楚,按著模塊與函數的方法可以很好的組織。
2、函數式編程(Functional)當談論函數式編程,會提到非常多的“函數式”特性。提到不可變數據,第一類對象以及尾調用優化,這些是幫助函數式編程的語言特征。提到mapping(映射),reducing(歸納),piplining(管道),recursing(遞歸),currying(科里化),以及高階函數的使用,這些是用來寫函數式代碼的編程技術。提到并行,惰性計算以及確定性,這些是有利于函數式編程的屬性。
最主要的原則是避免副作用,它不會依賴也不會改變當前函數以外的數據。
聲明式的函數,讓開發者只需要表達 “想要做什么”,而不需要表達 “怎么去做”,這樣就極大地簡化了開發者的工作。至于具體 “怎么去做”,讓專門的任務協調框架去實現,這個框架可以靈活地分配工作給不同的核、不同的計算機,而開發者不必關心框架背后發生了什么。
3、面向對象編程(Object-Oriented)并不是使用類才是面向對象編程。如果你專注于狀態改變和密封抽象,你就是在用面向對象編程。類只是幫助簡化面向對象編程的工具,并不是面向對象編程的要求或指示器。封裝是一個過程,它分隔構成抽象的結構和行為的元素。封裝的作用是分離抽象的概念接口及其實現。類只是幫助簡化面向對象編程的工具,并不是面向對象編程的要求或指示器。
隨著系統越來越復雜,系統就會變得越來越容易崩潰,分而治之,解決復雜性的技巧。面對對象思想的產生是為了讓你能更方便的理解代碼。有了那些封裝,多態,繼承,能讓你專注于部分功能,而不需要了解全局。
總結
命令式編程、面向對象編程、函數式編程,雖然受人追捧的時間點各不相同,但是本質上并沒有優劣之分。 面向對象和函數式、過程式編程也不是完成獨立和有嚴格的界限,在抽象出各個獨立的對象后,每個對象的具體行為實現還是有函數式和過程式完成。
三、重點說一下函數式編程 1.函數式編程中的一些基本概念(1)函數
函數式編程中的函數,這個術語不是指命令式編程中的函數(我們可以認為C++程序中的函數本質是一段子程序Subroutine),而是指數學中的函數,即自變量的映射(一種東西和另一種東西之間的對應關系)。也就是說,一個函數的值僅決定于函數參數的值,不依賴其他狀態。
在函數式語言中,函數被稱為一等函數(First-class function),與其他數據類型一樣,作為一等公民,處于平等地位,可以在任何地方定義,在函數內或函數外;可以賦值給其他變量;可以作為參數,傳入另一個函數,或者作為別的函數的返回值。
(2)純函數
純函數是這樣一種函數,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。不依賴外部狀態,不改變外部狀態。
比如Javascript里slice和splice,這兩個函數的作用相似。 slice符合純函數的定義是因為對相同的輸入它保證能返回相同的輸出。splice卻會嚼爛調用它的那個數組,然后再吐出來;這就會產生可觀察到的副作用,即這個數組永久地改變了。
var xs = [1,2,3,4,5]; // 純的 xs.slice(0,3); //=> [1,2,3] xs.slice(0,3); //=> [1,2,3] xs.slice(0,3); //=> [1,2,3] // 不純的 xs.splice(0,3); //=> [1,2,3] xs.splice(0,3); //=> [4,5] xs.splice(0,3); //=> []
(3)變量與表達式
純函數式編程語言中的變量也不是命令式編程語言中的變量(存儲狀態的內存單元),而是數學代數中的變量,即一個值的名稱。變量的值是不可變的(immutable),也就是說不允許像命令式編程語言中那樣能夠多次給一個變量賦值。比如說在命令式編程語言我們寫x = x + 1。函數式語言中的條件語句,循環語句等也不是命令式編程語言中的控制語句,而是一種表達式。
“表達式”(expression)是一個單純的運算過程,總是有返回值;
“語句”(statement)是執行某種操作(更多的是邏輯語句。),沒有返回值。
函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,而且都有返回值。比如在Scala語言中,if else不是語句而是三元運算符,是有返回值的。嚴格意義上的函數式編程意味著不使用可變的變量,賦值,循環和其他命令式控制結構進行編程。 當然,很多所謂的函數式編程語言并沒有嚴格遵循這一類的準則,只有某些純函數式編程語言,如Haskell等才是完完全全地依照這種準則設計的。
(4)狀態
首先要意識到,我們的程序是擁有“狀態”的。 想一想我們調試C++程序的時候,經常會在某處設置一個斷點。程序執行斷點就暫停了,也就是說程序停留在了某一個狀態上。這個狀態包括了當前定義的全部變量,以及一些當前系統的狀態,比如打開的文件、網絡的連接、申請的內存等等。具體保存的信息和語言有關系。比如使用過Matlab、R之類的科學計算語言的朋友會知道,在退出程序的時候它會讓你選擇是否要保存當前的session,如果保存了,下次打開時候它會從這個session開始繼續執行,而不是清空一切重來。你之前定義了一個變量x = 1,現在這個x還在那里,值也沒變。這個狀態就是圖靈機的紙帶。有了狀態,我們的程序才能不斷往前推進,一步步向目標靠攏的。函數式編程不一樣。函數式編程強調無狀態,不是不保存狀態,而是強調將狀態鎖定在函數的內部,不依賴于外部的任何狀態。更準確一點,它是通過函數創建新的參數或返回值來保存程序的狀態的。
狀態完全存在的棧上。
(5)函數柯里化
curry 的概念很簡單:將一個低階函數轉換為高階函數的過程就叫柯里化。
// 柯里化之前 function add(x, y) { return x + y; } add(1, 2) // 3 // 柯里化之后 function addX(y) { return function (x) { return x + y; }; } addX(2)(1) // 3
(6)函數組合(Pointfree:不使用所要處理的值,只合成運算過程)
為了解決函數嵌套過深,洋蔥代碼:h(g(f(x))),我們需要用到“函數組合”,我們一起來用柯里化來改他,讓多個函數像拼積木一樣。
const compose = (f, g) => (x => f(g(x))); var first = arr => arr[0]; var reverse = arr = arr.reverse(); var last = compose(first, reverse); last([1, 2, 3, 4, 5]); // 5
(7)聲明式與命令式代碼
在我們日常業務開發中,寫的代碼絕大多數都為命令式代碼;
我們通過編寫一條又一條指令去讓計算機執行一些動作,這其中一般都會涉及到很多繁雜的細節。
而聲明式就要優雅很多了,我們通過寫表達式的方式來聲明我們想干什么,而不是通過一步一步的指示。
//命令式 let CEOs = []; for (var i = 0; i < companies.length; i++) { CEOs.push(companies[i].CEO) } //聲明式 let CEOs = companies.map(c => c.CEO);2.函數式編程的特性
(1)高階函數
高階函數就是參數為函數或返回值為函數的函數。有了高階函數,就可以將復用的粒度降低到函數級別。相對于面向對象語言,高階函數的復用粒度更低。高階函數提供了一種函數級別上的依賴注入(或反轉控制)機制,在上面的例子里,sum函數的邏輯依賴于注入進來的函數的邏輯。很多GoF設計模式都可以用高階函數來實現,如Visitor,Strategy,Decorator等。比如Visitor模式就可以用集合類的map()或foreach()高階函數來替代。
(2)閉包
閉包的基礎是一等函數(First-class function)。閉包在形式上就是一個函數內部定義另一個函數,函數的堆棧在在函數返回后并不釋放,我們也可以理解為這些函數堆棧并不在棧上分配而是在堆上分配。
(3)訪問權限控制
js中的作用域
(4)延長變量生命周期
在面向對象語言里,函數內的變量都是在棧上分配的,函數調用完成后,棧銷毀,變量的生命周期結束。而對象是在堆分配的,會常駐內存,除非被手動或自動回收掉。
(5)函子
Functor(函子)遵守一些特定規則的容器類型。任何具有map方法的數據結構,都可以當作函子的實現。
Functor 是一個對于函數調用的抽象,我們賦予容器自己去調用函數的能力。把東西裝進一個容器,只留出一個接口 map 給容器外的函數,map 一個函數時,我們讓容器自己來運行這個函數,這樣容器就可以自由地選擇何時何地如何操作這個函數。
由于命令式編程語言也可以通過類似函數指針的方式來實現高階函數,函數式的最主要的好處主要是不變性帶來的。
(1)引用透明(Referential transparency)
引用透明(Referential transparency),指的是函數的運行不依賴于外部變量或”狀態”,只依賴于輸入的參數,任何時候只要參數相同,引用函數所得到的返回值總是相同的。其他類型的語言,函數的返回值往往與系統狀態有關,不同的狀態之下,返回值是不一樣的。這就叫”引用不透明”,很不利于觀察和理解程序的行為。
沒有可變的狀態,函數就是引用透明(Referential transparency)
(2)沒有副作用(No Side Effect)。(Lodash.js)
副作用(side effect),指的是函數內部與外部互動(最典型的情況,就是修改全局變量的值),產生運算以外的其他結果。函數式編程強調沒有”副作用”,意味著函數要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。函數即不依賴外部的狀態也不修改外部的狀態,函數調用的結果不依賴調用的時間和位置,這樣寫的代碼容易進行推理,不容易出錯。這使得單元測試和調試都更容易。
還有一個好處是,由于函數式語言是面向數學的抽象,更接近人的語言,而不是機器語言,代碼會比較簡潔,也更容易被理解。
(3)無鎖并發
沒有副作用使得函數式編程各個獨立的部分的執行順序可以隨意打亂,(多個線程之間)不共享狀態,不會造成資源爭用(Race condition),也就不需要用鎖來保護可變狀態,也就不會出現死鎖,這樣可以更好地進行無鎖(lock-free)的并發操作。尤其是在對稱多處理器(SMP)架構下能夠更好地利用多個處理器(核)提供的并行處理能力。
(4)惰性求值
惰性求值(lazy evaluation,也稱作call-by-need)是這樣一種技術:是在將表達式賦值給變量(或稱作綁定)時并不計算表達式的值,而在變量第一次被使用時才進行計算。
這樣就可以通過避免不必要的求值提升性能。(簡單例子&&)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105393.html
摘要:函數式編程的哲學就是假定副作用是造成不正當行為的主要原因。函數組合面向對象通常被比喻為名詞,而函數式編程是動詞。尾遞歸優化函數式編程語言中因為不可變數據結構的原因,沒辦法實現循環。 零、前言 說到函數式編程,想必各位或多或少都有所耳聞,然而對于函數式的內涵和本質可能又有些說不清楚。 所以本文希望針對工程師,從應用(而非學術)的角度將函數式編程相關思想和實踐(以 JavaScript 為...
摘要:參考鏈接面向對象編程模型現在的很多編程語言基本都具有面向對象的思想,比如等等,而面向對象的主要思想對象,類,繼承,封裝,多態比較容易理解,這里就不多多描述了。 前言 在我們的日常日發和學習生活中會常常遇到一些名詞,比如 命令式編程模型,聲明式編程模型,xxx語言是面向對象的等等,這個編程模型到處可見,但是始終搞不清是什么?什么語言又是什么編程模型,當你新接觸一門語言的時候,有些問題是需...
摘要:第一節函數式范式什么是函數式編程函數式編程英語或稱函數程序設計,又稱泛函編程,是一種編程范型,它將電腦運算視為數學上的函數計算,并且避免使用程序狀態以及易變對象。 第一節 函數式范式 1. 什么是函數式編程 函數式編程(英語:functional programming)或稱函數程序設計,又稱泛函編程,是一種編程范型,它將電腦運算視為數學上的函數計算,并且避免使用程序狀態以及易變對...
摘要:在函數式編程中數據在由純函數組成的管道中傳遞。函數式編程中函子是實現了函數的容器下文中將函子視為范疇,模型可表示如下但是在函數式編程中要避免使用這種面向對象的編程方式取而代之對外暴露了一個的接口也稱為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會有 3 篇文章,分別介紹什么是函數式編程、剖析函數...
閱讀 3574·2019-08-30 15:55
閱讀 1372·2019-08-29 16:20
閱讀 3655·2019-08-29 12:42
閱讀 2660·2019-08-26 10:35
閱讀 1010·2019-08-26 10:23
閱讀 3405·2019-08-23 18:32
閱讀 897·2019-08-23 18:32
閱讀 2891·2019-08-23 14:55