柯里化的前生今世(三) 語言和同像性

2021-09-23 17:33:24 字數 4328 閱讀 3794

按照故事情節的正常發展,我們這一篇該介紹racket語言的語法了。

可是,在大局觀上,我們還沒有達成共識。

對於乙個概念來說,我們不止要學會怎樣描述它,還要學會理解它的內涵。

因此,這篇還是在打基礎,俗稱,引言。。

本文將從目標語言和元語言,同像性(homoiconicity),引用等這些角度來深入的討論lisp,

淺嘗輒止,畢竟不是乙個好習慣。

當我們討論一件事物時,我們所使用的語言被稱為物件語言。

而當我們談論一種語言時,我們所使用的語言被稱為元語言。

在任何語言研究中,都有一種作為研究物件的語言,還有一種由研究者用來談論物件語言的元語言。

物件語言與元語言是相對而言的。

任何語言,無論它多麼簡單或者多麼複雜,當它作為被談論的物件的時候,它就是物件語言;

當它用來討論一種語言的時候,它就是元語言。

區分開目標語言和元語言,是學好lisp的第一步,也是理解lisp元程式設計的第一步。

日常生活中,我們有了這樣的認識。

我們所了解的漢字總是有限的,但是我們能說的話,卻是無限的。

可以說出任意長度的漢字序列。

程式語言也是如此。

有人說程式設計,不就是輸入a到z嗎,指的就是這個程式語言的「字母表」。

字母表所包含的字母,是有限的,但是可以寫出無限多個「句子」。

「語言」,正是這些「句子」的集合。

所謂形式語言,指的是用精確的數學,或機器可處理的公式,定義的語言。

相應的數學和電腦科學分支叫做形式語言與自動機理論,

它只研究語言的語法而不討論它的語義。

當初,為了研究語言的性質,人們從兩個角度出發,

乙個是從語言的識別角度來看,提出了自動機理論。

另乙個是從語言的生成角度來看,有喬姆斯基開創的形式語言理論。

這兩個理論之間,又是互相關聯的。

文法提供了一種方便的方法來定義「句子」的無限集。

為了描述語言的結構,john backus和peter naur創造了一種語言的描述方法,

稱為bnf(backus-naur form)。

expr ::= term .

term ::= factor .

factor ::= "(" expr ")".

bnf表示中的每一行,稱為乙個「產生式」,::=表示左邊的項可以由右邊的項來產生。

其中,用引號括起來的項,稱為「終結符」,相當於字面量。

不用引號括起來的項,稱為「非終結符」,它們可以由其他項組成。

是約定好了的符號,用來表示它包含的項可以出現0次或更多次。

常用的還有[…],用來表示,可以出現也可以不出現。

以上bnf描述了算術表示式的語法。

例如:1*(2+3),可以從expr開始生成出來,

expr

=> factor 「*」 factor

=> factor 「*」 「(」 expr 「)」

=> factor 「*」 「(」 term 「+」 term 「)」

=> 1 「*」 「(」 2 「+」 3 「)」

expr稱為「開始符號」。

綜上,乙個語言的所有終結符,非終結符,產生式,開始符號,

構成了這個語言的文法。

喬姆斯基,根據語言文法產生式的特點,把語言分為了4類。

不同的文法,能描述不能範圍的語言集合,雖然它們都是無限集。

0型文法,能力最強,可以產生遞迴可列舉語言。

1型文法,能力稍弱,可以產生上下文有關語言。

2型文法,能力次之,可以產生上下文無關語言。

3型文法,能力最弱,可以產生正則語言。

這些文法,建立了乙個從大到小,互相包含的,語言集合的層次關係。

例如:正則語言,一定是上下文無關語言,反之,則不成立。

其中,2型和3型文法用的最多,有特殊的名字,稱為,上下文無關文法,正則文法。

我們似乎發現了,這裡也出現了「正則」兩個字,難道與正規表示式有關?

確實,正規表示式,是正則文法的便利寫法。

正規表示式所描述的語言,就是正則語言。

了解過racket之後,我們發現racket程式都用一種稱為s表示式的語法寫成。

s表示式,是lisp語言的特色,它是二叉樹的一種線性編碼。

我們知道二叉樹是很重要的資料結構,可以用來儲存結構化的資料。例如:

*

/   \

*     *

/ \   / \

a   b c   d

二叉樹的每個節點,或者是葉節點,或者有2個子節點,葉節點可以用來儲存資料。

可是,這樣表示二叉樹,太麻煩了,不容易書寫。

於是,先哲們發明了「點對表示法」,((a . b) . (c . d))可以表示上面的二叉樹,

其中「.」表示節點。

s表示式是點對表示法的形式定義:

atom ::= num | symbol

s-exp ::= atom | "(" s-exp "." s-exp ")"

所以,s表示式或者是原子(atom),或者是遞迴的由其他s表示式構成的點對。

實際使用時,書寫s表示式,還要同時寫很多點號「.」,這也是一件麻煩的事情。

因此,lisp語言定義了一套s表示式的化簡規則。

(1)如果乙個點號右鄰乙個左括號,那麼就可以將這個點號,左括號以及匹配的右括號,一起去掉。

例如:(a . (b . c)) <=> (a b . c)

(2)如果乙個點號右鄰原子nil,那麼就可以把這個點號和原子nil,一起去掉。

例如:(a . (b . nil)) <=> (a b . nil) <=> (a b)

同像性,指的是程式和程式所操作的資料採用了統一編碼。

lisp語言使用了s表示式,例如,(fn x)

既可以看做是程式,用引數x呼叫函式fn,

也可以看做是資料,由符號fn和符號x構成的列表。

同像性使得我們,可以像處理資料一樣處理**。

做一些**轉換之類的工作,十分簡單。

例如,當遇到(fn x)時,

我們可以讓它先轉換成,

(begin

(display x)

(gn x))

然後再執行。

甚至也可以用來定義變數,

(define-with-display (f a)

(g a))

轉換成,

(define (f a)

(display a)

(g a))

這種**層面的轉換稱為「巨集」(macro)。

在lisp語言中,引用(quotation)是乙個很獨特的概念。

這與按引用傳參(call by reference)完全是兩碼事。

在lisp程式中,我們知道(+ 1 2)是乙個加法呼叫,

但是它也可以表示由3個符號+12構成的列表。

列表是資料,加法呼叫是程式,它們雖然採用了相同的編碼,可是我們沒有辦法區分。

首先想到的就是讓它們採用不同的編碼。例如:

我們把函式呼叫編碼為+[1;2],而列表編碼為(+ 1 2)

人們一開始也是這麼做的,+[1;2]稱為m表示式,(+ 1 2)稱為s表示式。

可是,後來人們發現,如果用lisp語言來處理lisp程式文字時,

不同的編碼,會增加難度,即,失去了同像性的種種優勢。

另一方面,程式主要是由函式呼叫組成的,把程式看成資料是更少見的一種場景。

所以,人們進行了以下編碼,

函式呼叫編碼為(+ 1 2)

而列表編碼為(quote (+ 1 2))

即,(+ 1 2)求值,會導致函式呼叫。

(quote (+ 1 2))求值,會得到乙個列表。

於是,我們就統一的用s表示式,完成了對程式和資料的相同編碼。

元語言形式語言

s表示式

同像性程式語言:實踐之路

lisp語言

函式的柯里化

問題描述 已知 fn 為乙個預定義函式,實現函式 curryit,呼叫之後滿足如下條件 1 返回乙個函式 a,a 的 length 屬性值為 1 即顯式宣告 a 接收乙個引數 2 呼叫 a 之後,返回乙個函式 b,b 的 length 屬性值為 1 3 呼叫 b 之後,返回乙個函式 c,c 的 le...

JS的防抖,節流,柯里化和反柯里化

將高頻率事件轉變為低頻率事件 定時器實現 實現思路 當觸發事件的時候,我們設定乙個定時器,再次觸發事件的時候,如果定時器存在,就不執行,直到delay時間後,定時器執行執行函式,並且清空定時器,這樣就可以設定下個定時器。當第一次觸發事件時,不會立即執行函式,而是在delay秒後才執行。而後再怎麼頻繁...

Dart語言中的currying(柯里化)實現

dart語言是谷歌開發的一款程式語言,谷歌移動開發框架flutter就是使用該語言作為開發語言 dart支援函式式程式設計,函式式程式設計可以實現的功能之一是currying,柯里化 currying是啥呢?維基百科裡給出的概念 是一種把接受多個引數的函式變換成接受乙個單一引數 最初函式的第乙個引數...