自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么我如此迷戀Lisp語言?

開發(fā) 前端
Lisp是一種很老的語言。非常的老。Lisp有很多變種,但如今已沒有一種語言叫Lisp的了。事實上,有多少Lisp程序員,就有多少種Lisp。這是因為,只有當(dāng)你獨自一人深入荒漠,用樹枝在黃沙上為自己喜歡的Lisp方言寫解釋器時,你才成為一名真正的Lisp程序員。

本文是從 Why I love Lisp 這篇文章翻譯而來。

這篇文章是我在Simplificator——我工作的地方——的一次座談內(nèi)容的摘錄,座談的題目叫做“為什么我喜歡Smalltalk語言和Lisp語言”。在此之前,我曾發(fā)布過一篇叫做“ 為什么我喜歡Smalltalk?”的文章。

 

[[46538]]

 

大漠黃沙 by Guilherme Jófili

Lisp是一種很老的語言。非常的老。Lisp有很多變種,但如今已沒有一種語言叫Lisp的了。事實上,有多少Lisp程序員,就有多少種Lisp。這是因為,只有當(dāng)你獨自一人深入荒漠,用樹枝在黃沙上為自己喜歡的Lisp方言寫解釋器時,你才成為一名真正的Lisp程序員。

目前主要有兩種Lisp語言分支:Common Lisp 和 Scheme,每一種都有無數(shù)種的語言實現(xiàn)。各種Common Lisp實現(xiàn)都大同小異,而各種Scheme實現(xiàn)表現(xiàn)各異,有些看起來非常的不同,但它們的基本規(guī)則都相同。這兩種語言都非常有趣,但我卻沒有在實際工作中用過其中的任何一種。這兩種語言中分別在不同的方面讓我苦惱,在所有的Lisp方言中,我最喜歡的是Clojure語言。我不想在這個問題上做更多的討論,這是個人喜好,說起來很麻煩。

Clojure,就像其它種的Lisp語言一樣,有一個REPL(Read Eval Print Loop)環(huán)境,你可以在里面寫代碼,而且能馬上得到運行結(jié)果。例如:

  1. 5  
  2. ;=> 5  
  3.  
  4. "Hello world"  
  5. ;=> "Hello world" 

通常,你會看到一個提示符,就像user>,但在本文中,我使用的是更實用的顯示風(fēng)格。這篇文章中的任何REPL代碼你都可以直接拷貝到Try Clojure運行。

我們可以像這樣調(diào)用一個函數(shù):

  1. (println "Hello World")  
  2. ; Hello World  
  3. ;=> nil 

程序打印出“Hello World”,并返回nil。我知道,這里的括弧看起來好像放錯了地方,但這是有原因的,你會發(fā)現(xiàn),他跟Java風(fēng)格的代碼沒有多少不同:

  1. println("Hello World") 

這種Clojure在執(zhí)行任何操作時都要用到括?。?/p>

  1. (+ 1 2)  
  2. ;=> 3 

在Clojure中,我們同樣能使用向量(vector):

  1. [1 2 3 4]  
  2. ;=> [1 2 3 4] 

還有符號(symbol):

  1. 'symbol  
  2. ;=> symbol 

這里要用引號('),因為Symbol跟變量一樣,如果不用引號前綴,Clojure會把它變成它的值。list數(shù)據(jù)類型也一樣:

  1. '(li st)  
  2. ;=> (li st) 

以及嵌套的list:

  1. '(l (i s) t)  
  2. ;=> (l (i s) t) 

定義變量和使用變量的方法像這樣:

  1. (def hello-world "Hello world")  
  2. ;=> #'user/hello-world  
  3.  
  4. hello-world  
  5. ;=> "Hello world" 

我的講解會很快,很多細(xì)節(jié)問題都會忽略掉,有些我講的東西可能完全是錯誤的。請原諒,我盡力做到最好。

在Clojure中,創(chuàng)建函數(shù)的方法是這樣:

  1. (fn [n] (* n 2))  
  2. ;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8> 

這顯示的又長又難看的東西是被編譯后的函數(shù)被打印出的樣子。不要擔(dān)心,你不會經(jīng)??吹剿鼈?。這是個函數(shù),使用fn操作符創(chuàng)建,有一個參數(shù)n。這個參數(shù)和2相乘,并當(dāng)作結(jié)果返回。Clojure和其它所有的Lisp語言一樣,函數(shù)的最后一個表達(dá)式產(chǎn)生的值會被當(dāng)作返回值返回。

如果你查看一個函數(shù)如何被調(diào)用:

  1. (println "Hello World") 

你會發(fā)現(xiàn)它的形式是,括弧,函數(shù),參數(shù),反括弧?;蛘哂昧硪环N方式描述,這是一個列表序列,序列的第一位是操作符,其余的都是參數(shù)。

讓我們來調(diào)用這個函數(shù):

  1. ((fn [n] (* n 2)) 10)  
  2. ;=> 20 

我在這里所做的是定義了一個匿名函數(shù),并立即應(yīng)用它。讓我們來給這個函數(shù)起個名字:

  1. (def twice (fn [n] (* n 2)))  
  2. ;=> #'user/twice 

現(xiàn)在我們通過這個名字來使用它:

  1. (twice 32)  
  2. ;=> 64 

正像你看到的,函數(shù)就像其它數(shù)據(jù)一樣被存放到了變量里。因為有些操作會反復(fù)使用,我們可以使用簡化寫法:

  1. (defn twice [n] (* 2 n))  
  2. ;=> #'user/twice  
  3.  
  4. (twice 32)  
  5. ;=> 64 

我們使用if來給這個函數(shù)設(shè)定一個最大值:

  1. (defn twice [n] (if (> n 50) 100 (* n 2)))) 

if操作符有三個參數(shù):斷言,當(dāng)斷言是true時將要執(zhí)行的語句,當(dāng)斷言是 false 時將要執(zhí)行的語句。也許寫成這樣更容易理解:

  1. (defn twice [n]  
  2.   (if (> n 50)  
  3.       100  
  4.       (* n 2))) 

非常基礎(chǔ)的東西。讓我們來看一下更有趣的東西。

假設(shè)說你想把Lisp語句反著寫。把操作符放到最后,像這樣:

  1. (4 5 +) 

我們且把這種語言叫做Psil(反著寫的Lisp...我很聰明吧)。很顯然,如果你試圖執(zhí)行這條語句,它會報錯:

  1. (4 5 +)  
  2. ;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0) 

Clojure會告訴你4不是一個函數(shù)(函數(shù)是必須是clojure.lang.IFn接口的實現(xiàn))。

我們可以寫一個簡單的函數(shù)把Psil轉(zhuǎn)變成Lisp:

  1. (defn psil [exp]  
  2. (reverse exp)) 

當(dāng)我執(zhí)行它時出現(xiàn)了問題:

  1. (psil (4 5 +))  
  2. ;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0) 

很明顯,我弄錯了一個地方,因為在psil被調(diào)用之前,Clojure會先去執(zhí)行它的參數(shù),也就是(4 5 +),于是報錯了。我們可以顯式的把這個參數(shù)轉(zhuǎn)化成list,像這樣:

  1. (psil '(4 5 +))  
  2. ;=> (+ 5 4) 

這回它就沒有被執(zhí)行,但卻反轉(zhuǎn)了。要想運行它并不困難:

  1. (eval (psil '(4 5 +)))  
  2. ;=> 9 

你開始發(fā)現(xiàn)Lisp的強大之處了。事實上,Lisp代碼就是一堆層層嵌套的列表序列,你可以很容易從這些序列數(shù)據(jù)中產(chǎn)生可以運行的程序。

如果你還沒明白,你可以在你常用的語言中試一下。在數(shù)組里放入2個數(shù)和一個加號,通過數(shù)組來執(zhí)行這個運算。你最終得到的很可能是一個被連接的字符串,或是其它怪異的結(jié)果。

這種編程方式在Lisp是如此的非常的常見,于是Lisp就提供了叫做宏(macro)的可重用的東西來抽象出這種功能。宏是一種函數(shù),它接受未執(zhí)行的參數(shù),而返回的結(jié)果是可執(zhí)行的Lisp代碼。

讓我們把psil傳化成宏:

  1. (defmacro psil [exp]  
  2. (reverse exp)) 

唯一不同之處是我們現(xiàn)在使用defmacro來替換defn。這是一個非常大的改動:

  1. (psil (4 5 +))  
  2. ;=> 9 

請注意,雖然參數(shù)并不是一個有效的Clojure參數(shù),但程序并沒有報錯。這是因為參數(shù)并沒有被執(zhí)行,只有當(dāng)psil處理它時才被執(zhí)行。psil把它的參數(shù)按數(shù)據(jù)看待。如果你聽說過有人說Lisp里代碼就是數(shù)據(jù),這就是我們現(xiàn)在在討論的東西了。數(shù)據(jù)可以被編輯,產(chǎn)生出其它的程序。這種特征使你可以在Lisp語言上創(chuàng)建出任何你需要的新型語法語言。

在Clojure里有一種操作符叫做macroexpand,它可以使一個宏跳過可執(zhí)行部分,這樣你就能看到是什么樣的代碼將會被執(zhí)行:

  1. (macroexpand '(psil (4 5 +)))  
  2. ;=> (+ 5 4) 

你可以把宏看作一個在編譯期運行的函數(shù)。事實上,在Lisp里,編譯期和運行期是雜混在一起的,你的程序可以在這兩種狀態(tài)下來回切換。我們可以讓psil宏變的羅嗦些,讓我們看看代碼是如何運行的,但首先,我要先告訴你do這個東西。

do是一個很簡單的操作符,它接受一批語句,依次運行它們,但這些語句是被整體當(dāng)作一個表達(dá)式,例如:

  1. (do (println "Hello") (println "world"))  
  2. ; Hello  
  3. ; world  
  4. ;=> nil 

通過使用do,我們可以使宏返回多個表達(dá)式,我們能看到更多的東西:

  1. (defmacro psil [exp]  
  2.   (println "compile time")  
  3.   `(do (println "run time")  
  4.        ~(reverse exp))) 

新宏會打印出“compile time”,并且返回一個do代碼塊,這個代碼塊打印出“run time”,并且反著運行一個表達(dá)式。這個反引號`的作用很像引號',但它的獨特之處是你可以使用~符號在其內(nèi)部解除引號。如果你聽不明白,不要擔(dān)心,讓我們來運行它一下:

  1. (psil (4 5 +))  
  2. ; compile time  
  3. ; run time  
  4. ;=> 9 

如預(yù)期的結(jié)果,編譯期發(fā)生在運行期之前。如果我們使用macroexpand,或得到更清晰的信息:

  1. (macroexpand '(psil (4 5 +)))  
  2. ; compile time  
  3. ;=> (do (clojure.core/println "run time") (+ 5 4)) 

可以看出,編譯階段已經(jīng)發(fā)生,得到的是一個將要打印出“run time”的語句,然后會執(zhí)行(+ 5 4)。println也被擴(kuò)展成了它的完整形式,clojure.core/println,不過你可以忽略這個。然后代碼在運行期被執(zhí)行。

這個宏的輸出本質(zhì)上是:

  1. (do (println "run time")  
  2.     (+ 5 4)) 

而在宏里,它需要被寫成這樣:

  1. `(do (println "run time")  
  2.      ~(reverse exp)) 

反引號實際上是產(chǎn)生了一種模板形式的代碼,而波浪號讓其中的某些部分被執(zhí)行((reverse exp)),而其余部分被保留。

對于宏,其實還有更令人驚奇的東西,但現(xiàn)在,它已經(jīng)很能變戲法了。

這種技術(shù)的力量還沒有被完全展現(xiàn)出來。按著" 為什么我喜歡Smalltalk?"的思路,我們假設(shè)Clojure里沒有if語法,只有cond語法。也許在這里,這并不是一個太好的例子,但這個例子很簡單。

cond 功能跟其它語言里的switch 或 case 很相似:

  1. (cond (= x 0) "It's zero"  
  2.       (= x 1) "It's one"  
  3.       :else "It's something else") 

使用 cond,我們可以直接創(chuàng)建出my-if函數(shù):

  1. (defn my-if [predicate if-true if-false]  
  2.   (cond predicate if-true  
  3.         :else if-false)) 

初看起來似乎好使:

  1. (my-if (= 0 0) "equals" "not-equals")  
  2. ;=> "equals"  
  3. (my-if (= 0 1) "equals" "not-equals")  
  4. ;=> "not-equals" 

但有一個問題。你能發(fā)現(xiàn)它嗎?my-if執(zhí)行了它所有的參數(shù),所以,如果我們像這樣做,它就不能產(chǎn)生預(yù)期的結(jié)果了:

  1. (my-if (= 0 0) (println "equals") (println "not-equals"))  
  2. ; equals  
  3. ; not-equals  
  4. ;=> nil 

把my-if轉(zhuǎn)變成宏:

  1. (defmacro my-if [predicate if-true if-false]  
  2.   `(cond ~predicate ~if-true  
  3.          :else ~if-false)) 

問題解決了:

  1. (my-if (= 0 0) (println "equals") (println "not-equals"))  
  2. ; equals  
  3. ;=> nil 

這只是對宏的強大功能的窺豹一斑。一個非常有趣的案例是,當(dāng)面向?qū)ο缶幊瘫话l(fā)明出來后(Lisp的出現(xiàn)先于這概念),Lisp程序員想使用這種技術(shù)。

C程序員不得不使用他們的編譯器發(fā)明出新的語言,C++和Object C。Lisp程序員卻創(chuàng)建了一堆宏,就像defclass, defmethod等。這全都要歸功于宏。變革,在Lisp里,只是一種進(jìn)化。

原文:http://www.aqee.net/why-i-love-lisp/

【編輯推薦】

  1. 天才的程序員使用Lisp語言
  2. 2010年12月編程語言排行榜:觸摸經(jīng)典語言化石之Lisp
  3. 是什么讓Lisp語言如此先進(jìn)?
  4. Lisp介紹之七個原始操作符
  5. 編程語言的王道:Lisp之美
責(zé)任編輯:陳貽新 來源: 外刊IT評論
相關(guān)推薦

2010-10-15 10:35:18

2018-05-02 12:34:48

2014-12-23 09:34:47

動態(tài)語言

2013-03-18 09:30:18

Lisp

2012-04-09 13:35:10

Instagram

2023-09-17 23:01:39

Python編程語言

2017-07-26 10:21:46

DockerLinux容器

2022-06-01 23:27:38

區(qū)塊鏈加密貨幣數(shù)字資產(chǎn)

2020-06-02 19:14:59

Kubernetes容器開發(fā)

2020-11-05 10:50:09

物聯(lián)網(wǎng)數(shù)據(jù)技術(shù)

2021-02-09 20:51:13

D 語言腳本編程語言

2018-06-15 21:26:13

PythonCrystal語言

2022-11-28 09:00:03

編程bug開發(fā)

2012-11-13 10:27:45

PythonGo編程語言

2021-03-29 16:32:03

軟件代碼程序員

2020-04-21 11:03:34

微服務(wù)數(shù)據(jù)工具

2022-11-21 18:02:04

前端測試

2021-10-26 10:12:04

技術(shù)債務(wù)軟件開發(fā)應(yīng)用程序

2023-04-10 15:41:35

2013-04-19 13:59:00

Apache Hado
點贊
收藏

51CTO技術(shù)棧公眾號