terminology - speaker - programming paradigm中文




函數式,聲明式和命令式編程 (10)

術語功能性,聲明性和命令性編程是什麼意思?


Nowadays, new focus: we need the old classifications?

The Imperative/Declarative/Functional aspects was good in the past to classify generic languages, but in nowadays all "big language" (as Java, Python, Javascript, etc.) have some option (typically frameworks ) to express with "other focus" than its main one (usual imperative), and to express parallel processes, declarative functions, lambdas, etc.

So a good variant of this question is "What aspect is good to classify frameworks today?" ... An important aspect is something that we can labeling "programming style" ...

Focus on the fusion of data with algorithm

A good example to explain. As you can read about jQuery at Wikipedia ,

The set of jQuery core features — DOM element selections, traversal and manipulation —, enabled by its selector engine (...), created a new "programming style", fusing algorithms and DOM-data-structures

So jQuery is the best (popular) example of focusing on a "new programming style" , that is not only object orientation, is " Fusing algorithms and data-structures ". jQuery is somewhat reactive as spreadsheets, but not "cell-oriented", is " DOM-node oriented "... Comparing the main styles in this context:

  1. No fusion : in all "big languages", in any Functional/Declarative/Imperative expression, the usual is "no fusion" of data and algorithm, except by some object-orientation, that is a fusion in strict algebric structure point of view.

  2. Some fusion : all classic strategies of fusion, in nowadays have some framework using it as paradigm... dataflow , Event-driven programming (or old domain specific languages as awk and XSLT )... Like programming with modern spreadsheets, they are also examples of reactive programming style.

  3. Big fusion : is "the jQuery style"... jQuery is a domain specific language focusing on " fusing algorithms and DOM-data-structures ".
    PS: other "query languages", as XQuery, SQL (with PL as imperative expression option) are also data-algorith-fusion examples, but they are islands , with no fusion with other system modules... Spring , when using find() -variants and Specification clauses, is another good fusion example.


命令式 - 表達式描述要執行的動作序列(關聯)

聲明性表達式是對程序行為有貢獻的聲明(關聯性,交換性,冪等性,單調性)

功能 - 表達只有效果才有價值 ; 語義支持等式推理


命令式編程意味著任何編程風格,其中您的程序由描述計算機如何執行操作的指令組成。

聲明式編程意味著任何風格的編程,其中您的程序是對問題或解決方案的描述 - 但沒有明確說明工作將如何完成

函數式編程通過評估函數的函數和函數來進行編程......作為(嚴格定義的)函數式編程意味著通過定義無副作用的數學函數來進行編程,因此它是一種聲明式編程形式,但它不是唯一的聲明式編程

邏輯編程 (例如在Prolog中)是聲明式編程的另一種形式。 它涉及通過判斷一個邏輯陳述是否真實(或是否可以滿足)來進行計算。 該計劃通常是一系列事實和規則 - 即描述而不是一系列指令。

術語重寫 (例如CASL)是聲明性編程的另一種形式。 它涉及代數術語的符號轉換。 它與邏輯編程和函數式編程完全不同。


Declarative programming is programming by expressing some timeless logic between the input and the output, for instance, in pseudocode, the following example would be declarative:

def factorial(n):
  if n < 2:
    return 1
  else:
    return factorial(n-1)

output = factorial(argvec[0])

We just define a relationship called the 'factorial' here, and defined the relationship between the output and the input as the that relationship. As should be evident here, about any structured language allows declarative programming to some extend. A central idea of declarative programming is immutable data, if you assign to a variable, you only do so once, and then never again. Other, stricter definitions entail that there may be no side-effects at all, these languages are some times called 'purely declarative'.

The same result in an imperative style would be:

a = 1
b = argvec[0]
while(b < 2):
  a * b--

output = a

In this example, we expressed no timeless static logical relationship between the input and the output, we changed memory addresses manually until one of them held the desired result. It should be evident that all languages allow declarative semantics to some extend, but not all allow imperative, some 'purely' declarative languages permit side effects and mutation altogether.

Declarative languages are often said to specify 'what must be done', as opposed to 'how to do it', I think that is a misnomer, declarative programs still specify how one must get from input to output, but in another way, the relationship you specify must be effectively computable (important term, look it up if you don't know it). Another approach is nondeterministic programming, that really just specifies what conditions a result much meet, before your implementation just goes to exhaust all paths on trial and error until it succeeds.

Purely declarative languages include Haskell and Pure Prolog. A sliding scale from one and to the other would be: Pure Prolog, Haskell, OCaml, Scheme/Lisp, Python, Javascript, C--, Perl, PHP, C++, Pascall, C, Fortran, Assembly


Imperative programming: telling the “machine” how to do something, and as a result what you want to happen will happen.

Declarative programming: telling the “machine” what you would like to happen, and letting the computer figure out how to do it.

Example of imperative

function makeWidget(options) {
    const element = document.createElement('div');
    element.style.backgroundColor = options.bgColor;
    element.style.width = options.width;
    element.style.height = options.height;
    element.textContent = options.txt;

    return element;
}

Example of declarative

function makeWidget(type, txt) {
    return new Element(type, txt);
}

Note: The difference is not one of brevity or complexity or abstraction. As stated, the difference is how vs what .


Some good answers here regarding the noted "types".

I submit some additional, more "exotic" concepts often associated with the functional programming crowd:

  • Domain Specific Language or DSL Programming: creating a new language to deal with the problem at hand.
  • Meta-Programming : when your program writes other programs.
  • Evolutionary Programming : where you build a system that continually improves itself or generates successively better generations of sub-programs.

在寫這篇文章的時候,這個頁面上的頂部投票答案在聲明式和命令式的定義上是不精確和混亂的,包括引用維基百科的答案。 一些答案以不同的方式混合了術語。

無論公式如何變更單元格,還請參閱為何解釋電子表格程序的聲明。

另外,有幾個答案聲稱函數式編​​程必須是聲明式的一個子集。 在這一點上,這取決於我們是否將“功能”與“程序”區分開來。 讓我們首先處理命令式和聲明式。

聲明式表達的定義

唯一可以區分聲明式表達式和命令式表達式的屬性是其子表達式的參考透明度(RT)。 所有其他屬性或者在兩種表達式之間共享,或者從RT派生。

100%聲明性語言(即其中每個可能表達式都是RT的語言)不會(在其他RT需求中)允許存儲值的變異,例如HTML和Haskell的大部分。

RT表達的定義

RT通常被稱為“無副作用”。 術語效應沒有一個確切的定義,所以有些人不同意“無副作用”與RT相同。 RT有一個精確的定義 。

由於每個子表達式在概念上都是一個函數調用,因此RT要求函數(即被調用函數內的表達式)的實現可能不會訪問該函數外部的可變狀態(訪問可變局部狀態為允許)。 簡而言之,函數(實現)應該是純粹的

純函數的定義

一個純粹的功能通常被認為是“沒有副作用”。 術語效應沒有明確的定義,所以有些人不同意。

純函數具有以下屬性。

  • 唯一可觀察的輸出是返回值。
  • 唯一的輸出依賴是參數。
  • 參數在任何輸出生成之前完全確定。

請記住,RT適用於表達式(其中包括函數調用),純度適用於函數的(實現)。

製作RT表達式的不純功能的一個不為人知的例子是並發性,但這是因為中斷抽象層的純度被破壞了。 你並不需要知道這一點。 要製作RT表達式,可以調用純函數。

RT的衍生屬性

為聲明性編程引用的任何其他屬性,例如維基百科使用的1999年引用 ,都來自RT,或與命令式編程共享。 從而證明我的確切定義是正確的。

請注意, 外部值的不變性是RT要求的一個子集。

  • 聲明性語言沒有循環控制結構,例如forwhile ,因為由於不變性 ,循環條件永遠不會改變。

  • 聲明性語言除了嵌套函數順序(又稱邏輯依賴性)之外不表示控制流,因為由於不可變性 ,其他評估順序選擇不會改變結果(見下文)。

  • 聲明性語言表達邏輯“步驟”(即嵌套的RT函數調用順序),但是每個函數調用是否是更高級別的語義(即“做什麼”)不是聲明性編程的要求。 與命令的區別在於, 由於不變性 (即更一般的RT),這些“步驟”不能取決於可變狀態,而僅取決於所表達的邏輯的關係順序(即函數調用的嵌套順序,也就是子表達式)。

    例如,只有在段落中的子表達式(即標籤)被評估之後,才能顯示HTML段落<p> 。 由於標籤層次結構的邏輯關係(子表達式的嵌套 ,它們是類似嵌套的函數調用 ),因此沒有可變狀態,只有順序依賴性。

  • 因此,存在不變性(更一般地為RT)的派生屬性,即聲明性表達式表達組成部分(即子表達式函數參數)的邏輯關係而不表示可變狀態關係。

評估順序

當任何函數調用不是RT時(即函數不是純粹的),子表達式的評估順序的選擇只能給出不同的結果,例如在函數內訪問某個函數外部的某個可變狀態。

例如,給定一些嵌套表達式,例如f( g(a, b), h(c, d) ) ,如果函數fgh是純的,函數參數的急切和懶惰的評估將得到相同的結果。

而如果函數fgh不是純粹的,則評估順序的選擇可以給出不同的結果。

請注意,嵌套表達式是概念上嵌套的函數,因為表達式運算符只是偽裝成一元前綴,一元後綴或二進制中綴表示法的函數調用。

切線方向,如果所有標識符(例如abcd )在任何地方都不可變 ,程序外部的狀態不能被訪問(即I / O),並且沒有抽象層破損,則函數總是純粹的。

順便說一句,Haskell具有不同的語法, f (gab) (hcd)

評估訂單詳情

函數是從輸入到輸出的狀態轉換(而不是可變的存儲值)。 對於調用函數的RT組合,這些狀態轉換的執行順序是獨立的。 每個函數調用的狀態轉換與其他函數調用的狀態轉換相互獨立,因為缺乏副作用以及RT函數可能被其緩存值替換的原則。 為了糾正一個流行的誤解 ,儘管Haskell的IO monad 可以說是不純的,並且對於程序外部的World狀態也是必不可少的,但是純粹的monadic構成總是聲明式和RT ,但是從下面的警告意義上講, - 影響是孤立的)。

急切的評估意味著函數參數在函數被調用之前被評估,懶惰評估意味著直到(和如果)它們在函數內被訪問, 參數才被評估

定義 :函數參數在函數定義站點聲明,函數參數在函數調用站點提供。 知道參數參數之間的區別。

從概念上講,所有的表達式都是函數調用的組成部分,例如常量是沒有輸入的函數,一元運算符是帶有一個輸入的函數,二進制中綴運算符是帶有兩個輸入的函數,構造函數是函數,甚至是控制語句(例如, , while )可以用功能建模。 這些 參數函數的順序 (不要與嵌套的函數調用順序混淆)不會被語法聲明,例如, f( g() )可能急於評估g然後f的結果,或者它只能評估ff內需要結果時懶散地評估g

警告,沒有圖靈完整的語言(即允許無限遞歸)是完全的陳述性的,例如懶惰評估引入了記憶和時間不確定性。 但是,由於評估順序的選擇,這些副作用僅限於內存消耗,執行時間,延遲,非終止以及外部滯後,從而導致外部同步。

功能編程

因為聲明式編程不能有循環,所以迭代的唯一方法就是函數遞歸。 從這個意義上說,函數式編程與聲明式編程有關。

函數式編程不限於聲明式編程 。 功能組成可以與子類型相對照 ,特別是關於表達問題 ,其中擴展可以通過添加子類型或功能分解來實現。 擴展可以是兩種方法的混合

函數式編程通常使函數成為第一類對象,這意味著函數類型可以出現在任何其他類型的語法中。 結果是功能可以輸入和操作功能,從而通過強調功能組合來分離關注點,即分離確定性計算的子計算之間的依賴關係。

例如,對於可以應用於集合中每個元素的無數個可能的專用操作中的每一個,編寫獨立的函數 (並且如果函數也必須是聲明式的,則使用遞歸而不是循環),函數式編程採用可重複使用的迭代功能,例如mapfoldfilter 。 這些迭代函數輸入一個一流的專用動作函數。 這些迭代函數迭代集合併為每個元素調用輸入專用操作函數。 這些動作函數更簡潔,因為它們不再需要包含循環語句來迭代集合。

但是請注意,如果函數不是純粹的,那麼它確實是一個過程。 我們也許可以爭辯說,使用不純功能的函數式編程實際上是程序編程。 因此,如果我們同意聲明式表達式是RT,那麼我們可以說過程式編程不是聲明式編程,因此我們可能會認為函數式編程總是RT,並且必須是聲明式編程的一個子集。

排比

具有一流功能的這種功能組合可以通過分離獨立功能來表達平行度深度

布倫特原理:工作w和深度d的計算可以在時間O(max(w / p,d))中的p處理器PRAM中實現。

並發性和並行性都需要聲明性編程 ,即不可變性和RT。

那麼Parallelism == Concurrency從何而來的危險假設呢? 這是帶有副作用的語言的自然結果:當你的語言在各處都有副作用時,任何時候當你嘗試做多件事情時,你基本上都會由於每次操作產生的交叉影響而導致非確定性。 所以在副作用語言中,獲得併行性的唯一方法是並發性; 因此我們經常看到兩者混淆並不奇怪。

FP評估順序

請注意,評估順序也會影響功能組合的終止和性能副作用。

渴望(CBV)和懶惰(CBN)是類別決鬥[ 10 ],因為它們顛倒了評估順序,即分別評估外部函數還是內部函數。 想像一個顛倒的樹,然後渴望從功能樹分支中評估分支層次結構中的頂層功能樹幹; 而懶惰評估從樹幹到分支提示。 Eager沒有連接產品(“和”,a / k / a分類“產品”),懶惰沒有分離的副產品(“或”,a / k / a分類“總和”)[ 10 ]。

性能

  • 急於

    與非終止一樣,渴望過於渴望聯結的功能組成,即組成控制結構做不必要的工作,而不是懶惰的工作。 example ,當它由一個以第一個真實元素為終點的折疊組成時,熱切而不必要地將整個列表映射到布爾值。

    這種不必要的工作是聲稱“高達”一個額外的日誌因素在渴望與懶惰的連續時間複雜性的原因,都與純函數。 一種解決方案是使用函子(例如列表)和懶惰的構造函數(即渴望使用可選的懶惰產品),因為急切的渴望不正確源於內部函數。 這是因為產品是建設性的類型,即初始代數在初始定點上的歸納類型[ 10 ]

  • 與非終止一樣,懶惰對於分離功能組成來說太懶惰,即共誘導終結可能晚於必要發生,導致遲到的不必要的工作和非確定性,而不是急於求助[ 10 ] [ 10 ] 。 終結點的例子是狀態,定時,非終止和運行時異常。 這些都是必要的副作用,但即使是純粹的聲明性語言(例如Haskell),在命令式IO monad中也存在狀態(注意:並非所有monad都是必要的!)隱含在空間分配中,並且時序是相對於命令的狀態真實世界。 因為懶惰懶惰的不正確來自外部函數 (參見非終止部分中的示例,其中==是外部二元運算符函數),所以即使使用可選的保留副本產品也會使用懶惰。 這是因為副產品受終結限制,即共誘導類型與最終代數上的最終代數[ 10 ]。

    由於所聲明的函數層次結構與運行時評估順序之間不一致 ,因此懶惰會導致對延遲和空間函數的設計和調試產生不確定性,其調試可能超出大多數程序員的能力。 惰性計算的懶惰純函數可能潛在地在運行時引入先前未見過的非終止。 相反,懶惰評估的渴望純函數可能會潛在地在運行時引入先前看不見的空間和延遲不確定性。

未結束

在編譯時,由於Haling問題和圖靈完備語言中的相互遞歸,函數通常不能保證終止。

  • 急於

    對於HeadTail的結合,如果HeadTail不結束,則分別使用List( Head(), Tail() ).tail == Tail()List( Head(), Tail() ).head == Head()不是真的,因為左側沒有,右側是終止。

    而懶惰雙方終止。 因此,渴望對結合產品過於渴望,並且在沒有必要的情況下不終止(包括運行時異常)。

  • 懶惰但不渴望,對於12 List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail ,如果f不終止,那麼List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail是不正確的,因為左側終止,而右側不是。

    然而,由於雙方都渴望終止平等測試,所以永遠都不會達成。 因此,惰性對於不連續的副產品來說太懶,在這些情況下,在做了比渴望更多的工作之後,無法終止(包括運行時異常)。

[ 10 ]聲明性連續性和分類對偶性,Filinski,章節2.5.4 CBV和CBN的比較,以及3.6.1 SCL中的CBV和CBN。

[ 10 ]聲明性延續和分類對偶,Filinski,2.2.1節產品和副產品,2.2.2終端和初始對象,2.5.2 CBV帶有惰性產品,2.5.3 CBN帶有渴望副產品。


簡而言之:

命令式語言規定了計算機按順序執行的一系列指令(執行此操作,然後執行該操作)。

聲明性語言聲明了一組關於哪些輸出應該從哪些輸入中產生的規則(例如,如果你有A,那麼結果是B)。 引擎將這些規則應用於輸入,並給出輸出。

函數式語言聲明了一組數學/邏輯函數,它們定義了輸入如何轉換為輸出。 例如。 f(y)= y * y。 它是一種聲明性語言。


這些沒有任何非模棱兩可的客觀定義。 以下是如何定義它們:

勢在必行 - 重點在於計算機應採取的步驟,而不是計算機將執行什麼操作 (例如C,C ++,Java)。

聲明式 - 重點在於計算機應該做什麼,而不是它應該如何去做(例如SQL)。

功能性 - 重點關注遞歸的聲明性語言的子集


簡而言之,編程風格越強調什麼(做)將How(細節)的細節抽像出來,這種風格被認為是更具聲明性的。勢在必行,情況正好相反。函數式編程與聲明式風格相關聯。