1. 程式人生 > >Lisp的給力特性 V S Python3) -- 第一篇

Lisp的給力特性 V S Python3) -- 第一篇

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

騰訊微博 -- 討論新聞組 -- 程式碼庫 -- 豆瓣

BS,Gosling,Anders,Guido都要被打屁股?

以前就聽說過Lisp被很多人認為是世界上最好的語言,但是它是那麼的古老,這種言論很可能來自於不能進化到學習Ruby,Python的老古董,所以其實也沒有太在意。
Lisp(這裡特指Common lisp,下同)1978年被設計完成,是個多麼古老的語言啊。。。。卻總是不停的聽到太多的Lisp的好話,總是感覺有些難以相信,BS說,"一個新設計的語言不比老的好,那新語言的設計者是要被打屁股的",假如Lisp還是世界上最好的語言,那麼從BS自己,到James Gosling到Anders到Guido van Rossum 不都要被打屁股啊?你說我能輕易相信嗎?
如果我們把流行的程式語言,以這樣的順序排列:Java、Perl、Python、Ruby。你會發現,排在越後面的語言,越像Lisp。

這個話真的可信嗎?
正好碰到一篇妄圖說服我的文章《Features of Common Lisp》,眼見為實,是騾子是馬,跑起來看看^^本文以Features一文為線索,(標題翻譯的不好,請童鞋們見諒)與Python作為對比,C++啥的就都不太好意思拿出來比了,因為總是可以以效率為理由搪塞過去,但是,有可能我還是順帶提及一下。但是,總的來說,這是在Lisp的領地上戰鬥,,C++,Python是很吃虧的,但是作為已經足夠強大的語言,還能夠自我革新,Python的最新版本(3.1.1,以下沒有特指,均指此版本)能夠怎麼應付,我拭目以待。特別提及,相比Lisp實現,CPython的執行速度慢得驚人,甚至差5-10倍,對於一個犧牲速度來換取表達性的語言,要是比不過速度比自己快那麼多的語言,實在有些說不過去,即使是在別人的地盤..........迎戰的Lisp是lispbox-0.7版本中整合的common lisp。

 

Lisp特性列表

強大抽象的數學計算(Rich, exact arithmetic):


大數(Bignums):不用當心溢位。
Lisp:
CL-USER> (expt (expt (expt (expt 10 10) 10) 10) 10)
100000000000000000000000000000000000...

這個貌似還行,C++雖然是需要通過外部庫來處理,(在新標準中有過關於大數的提案,我以前還翻譯過一篇《(N1744)Big Integer Library Proposal for C++0x》)
但是Python對大數的支援還是非常不錯的,特別提及,在Python2.1時代,雖然有大數支援,但是你得自己指定,假如都是用普通整數,還是會溢位。在2.2以後版本中已預設會進行型別提升。(參考PEP237)呵呵,這點挺符合越新的語言(在Python來看,也就是越新的版本越接近)也就是越接近Lisp的理論。


Python:
>>> ((((10 ** 10) ** 10) ** 10) ** 10)
1000000000000000000000000000000000000000000.....

分數(Rational numbers):保留成比例的形式。
Lisp:
CL-USER> (+ 5/9 3/4)
47/36

這個很牛很牛。。。。我目前懂的語言,(C/C++,Lua,Python,Objc以後提及均表示此意)還沒有哪個是這樣計算分數的,給你的都是浮點數。
特別提及一點,在Python2.x時代,上面的整數運算會像C++中那樣,直接變成整數(5/9也就是0),但是新版本中已經不那麼扭曲了。

Python 2.6.4:
>>> 5 / 9
0

Python3:
>>> 5 / 9
0.5555555555555556
我很遺憾的表示,同樣的,python3比2.X版本更加像lisp,但是還不是足夠像。


複數(Complex numbers):

 

內建支援

Lisp:
CL-USER> (* 2 (+ #c(10 5) 4))
#C(28 10)

這個也還算方便,雖然我平時好像也用不上,C++中得通過庫處理。Python也內建支援。

Python:
>>> (((10 + 5j) + 4) * 2)
(28+10j)

相對來說,以近似偽碼著稱的Python表達還是更加清晰一些。

 

統一的引用(Generalized references):

Lisp:
CL-USER> (defvar *colours* (list 'red 'green 'blue))
*COLOURS*
CL-USER> (setf (first *colours*) 'yellow)
YELLOW
CL-USER> *colours*
(YELLOW GREEN BLUE)
CL-USER> (push 'red (rest *colours*))
(RED GREEN BLUE)
CL-USER> *colours*
(YELLOW RED GREEN BLUE)

Lisp的操作都是使用引用對列表進行操作,你可以改變這個列表,實際操作的是同一個列表,就像你使用了rest操作,並對其進行push,但是實際也還是會改變原來的colours,因為rest返回的也還是引用而不是臨時變數,這個特性看起來很有意思,有些特殊,具體的使用範圍我還不是太清除(因為畢竟沒有lisp編寫大型的程式)


比如:


Python:

>>> l
['red', 'yellow', 'green', 'blue']
>>> l ;
['red', 'yellow', 'green', 'blue']
>>> l = ["red", "green", "blue"]
>>> l[0] = "yellow"
>>> l
['yellow', 'green', 'blue']
>>> l[1:]
['green', 'blue']
>>> l2 = l[1:].insert(0, "red")
>>> l2
>>> l
['yellow', 'green', 'blue']


需要注意的是,l[1:].insert(0, "red")操作是不會返回['red','green','blue']的,這樣你臨時的變數都獲取不到,同樣的,用切片操作來模擬的話,不僅沒有返回值,原列表更不會改變,因為切片後的是臨時變數,而不是引用。

 

多重值(Multiple values):

Lisp:
CL-USER> (floor pi)
3
0.14159265358979312D0

有簡單的內建語法支援多個值,因此能方便的讓函式返回多個值。此點C++就靠其他手段吧,比如異常ugly的用傳指標/引用,然後再通過指標/引用傳出去,雖然用了這麼多年的C++了,這種方式也習慣了,但是一比較,就知道那不過是個因為語言的抽象能力太弱,使用的walk round的辦法而已。 Python還是可以的。

雖然,Python的floor不返回兩個值。

Python:
>>> import math
>>> math.floor(math.pi)
3

但是,你的確是可以返回多個值。


Python:
>>> def myFloor(x):
    return math.floor(x), x - math.floor(x)

>>> myFloor(math.pi)
(3, 0.14159265358979312)

但是,需要特別注意的是,這只是個假象......因為實際上是相當於將返回值作為一個tuple返回了。


Lisp:
CL-USER> (+ (floor pi) 2)
5

在計算時,讓第一個多重值的第一個變數作為計算的變數,所以非常方便。

因為Python的返回值如上面所言,其實是用tuple模擬多個返回值的,不要奢望了。


Python:

>>> myFloor(math.pi) + 1
Traceback (most recent call last):
  File "<pyshell#58>", line 1, in <module>
    myFloor(math.pi) + 1
TypeError: can only concatenate tuple (not "int") to tuple


不過,lua倒是可以,可能lua還是從lisp那吸收了很多東西吧:


Lua(5.1.2以下同):

> math.floor(math.pi)
> print(math.floor(math.pi))
3
> function myFloor(x)
>> return math.floor(x), x - math.floor(x)
>> end
> print(myFloor(math.pi)+ 1)
4


而且在Lisp中可以很方便的使用多重值的第二個值。(通過multiple-value-bind)


Lisp:

CL-USER> (multiple-value-bind (integral fractional)
         (floor pi)
       (+ integral fractional))
3.141592653589793D0


Python因為返回的是tuple,指定使用其他值倒是很方便,包括第一個(雖然不是預設使用第一個)


Python:

>>> myFloor(math.pi)
(3, 0.14159265358979312)
>>> myFloor(math.pi)[0] + myFloor(math.pi)[1]
3.141592653589793


最後,即使這樣Lisp在表達方面還是有優勢的,因為在lisp中floor只計算了一次,而python的表達方式得多次計算,除非起用臨時變數。

 

巨集(Macros):

lisp的巨集有點像其他語言中的函式,但是卻是在編譯期展開(這就有點想inline的函數了),但是在Lisp的fans那裡,這被稱作非常非常牛的syntactic abstraction(語法抽象),同時用於支援超程式設計,並且認為可以很方便的創造自己的領域語言。目前我不知道到底與C++的巨集(其實也是一樣的編譯期展開),還有比普通函式的優勢在哪。(原諒我才學Lisp沒有幾天)

LOOP巨集(The LOOP macro):
Lisp:
CL-USER> (defvar *list*
       (loop :for x := (random 1000)
          :repeat 5
          :collect x))
*LIST*
CL-USER> *list*
(441 860 581 120 675)

這個我有些不明白,難道在Lisp的那個年代,語言都是迴圈這個概念的。。。。導致,迴圈都是一個很牛的特性?

繼續往下看就牛了


Lisp:

CL-USER> (loop :for elt :in *list*
        :when (oddp elt)
        :maximizing elt)
675


when,max的類SQL尋找語法


Lisp:

CL-USER> (loop :for elt :in *list*
        :collect (log elt) :into logs
        :finally
        (return (loop :for l :in logs
               :if (> l 5.0) :collect l :into ms
               :else :collect l :into ns
               :finally (return (values ms ns)))))
(6.089045 6.7569323 6.364751 6.514713)
(4.787492)


遍歷並且進行分類。


我突然想到最近Anders在一次關於語言演化的演講中,講到關於宣告式程式設計與DSL例子,用的是最新加入.Net平臺的LINQ:


他表示,語言是從:

Dictionary<string, Grouping> groups = new Dictionary<string, Grouping>();
foreach (Product p in products)
{
    if (p.UnitPrice >= 20)
    {
        if (!groups.ContainsKey(p.CategoryName))
    {
        Grouping r = new Grouping();
        r.CategoryName = p.CategoryName;
        r.ProductCount = 0;
        groups[p.CategoryName] = r;
    }
    groups[p.CategoryName].ProductCount++;
    }
}

List<Grouping> result = new List<Grouping>(groups.Values);
result.Sort(delegate(Grouping x, Grouping y)
{
    return x.ProductCount > y.ProductCount ? -1 :
    x.ProductCount < y.ProductCount ? 1 : 0;
}

);


這種形式到:


LINQ:

var result = products
 .Where(p => p.UnitPrice >= 20)
 .GroupBy(p => p.CategoryName)
 .OrderByDescending(g => g.Count())
 .Select(g => new { CategoryName = g.Key, ProductCount = g.Count() });


看看與Lisp有多像吧(連名字都像^^)。。。。。(具體例子參見《程式語言的發展趨勢及未來方向(2):宣告式程式設計與DSL》)

而,Anders提到這個是在2010年。。。。。Lisp是1958年的語言。。。這個恐怖了。。。

突然,《為什麼Lisp語言如此先進?》裡面的話,“程式語言現在的發展,不過剛剛趕上1958年Lisp語言的水平。當前最新潮的程式語言,只是實現了他在1958年的設想而已。 ”在耳邊響起。。。。。。因為Anders的例子更有意思,Python那簡單的while,for迴圈,我就不列舉了。

 

Format函式(The FORMAT function):

Lisp:

CL-USER> (defun format-names (list)
  (format nil "~{~:(~a~)~#[.~; and ~:;, ~]~}" list))

FORMAT-NAMES
CL-USER>  (format-names '(doc grumpy happy sleepy bashful sneezy dopey))
"Doc, Grumpy, Happy, Sleepy, Bashful, Sneezy and Dopey."
CL-USER> (format-names '(fry laurie))
"Fry and Laurie."

CL-USER> (format-names '(bluebeard))
"Bluebeard."


Format函式現在的語言基本都有,連C/C++語言都有,為啥值得一提?因為lisp的format更加強大。。。。。強大到可以自動遍歷list(而lisp的最主要結構就是list)。上面的format可能感覺有些不好理解,其實主要是因為lisp中不用%而是用~,總體來說,不比C系語言的format更難理解,但是更加強大。最強大的地方就是~{~}組合帶來的遍歷功能。當然,最後一個單詞帶來的“and”,也是很震撼。。。這些都不是用一系列的迴圈加判斷實現的,用的是一個format,強大到有點正則表示式的味道。


看python同樣的例子,就能夠理解了,要實現同樣的功能,必須得加上外部的迴圈。

Python:

>>>import sys

>>>def format_name(list):
    if len(list) > 0:
        sys.stdout.write(list[0])
    if len(list) > 1:
        for index in range(1, len(list) - 1):
            sys.stdout.write(", %s" % list[index])
        sys.stdout.write(" and %s" % list[-1])

>>> format_name(l)
a, b and c
>>> format_name(l[0])
a
>>> format_name(l[0:2])
a and b


哪個更加簡單,一目瞭然,lisp的format函式的確強大,即使在python3上也無法有可以媲美的東西。(新的format模版也好不到哪裡去)

 

 

Functional functions:(不知道怎麼翻譯)

函式是第一類值,可以被動態建立,作為引數傳入,可以被return,這些特性相當強大。這也算是函式類語言最強大的特點之一了。(也許不需要之一吧)記得

Lisp:

CL-USER> (sort (list 4 2 3 1) #'<)
(1 2 3 4)


回顧一下C/C++中的函式指標,C++中的函式物件有多麼麻煩,看到這裡差不多要吐血了。(PS:在新的標準中可能會新增進原Boost中的Function庫,還是不錯的,以前寫過一篇關於這個的,這同樣也印證越現代的語言是向越來越像Lisp的方向進化,只是在C++中,表現為通過庫來彌補語言本身的不足

Python,lua的函式都是第一類值,(這裡有個講Python的FP程式設計的文章,非常不錯)所以用起來一樣的方便,只是python原生的sorted倒是不支援類似的操作:

sorted(iterable[, key][, reverse])

不過我們可以自己實現。


Python:(改自這裡的實現

>>> def sort(L, f):
    if not L:
        return []
    return sort([x for x in L[1:] if f(x , L[0])], f) + L[0:1] + /
        sort([x for x in L[1:] if not f(x, L[0])], f)

>>> def less(a, b):
    if a < b:
        return True
    else:
        return False

>>> def greater(a, b):
    if a > b:
        return True
    else:
        return False
>>> l = [4, 2, 3, 1]
>>> sort(l, less)
[1, 2, 3, 4]

>>> sort(l, greater)
[4, 3, 2, 1]


總的來說,在這個函式程式設計的核心領域,Python還是不錯的,看看上面的文章就知道,用Python也完全可以寫出類似FP的程式碼,只是因為Python的OOP特性的存在,大部分人完全忽略了其FP特性而已。

為什麼Lisp語言如此先進?》裡面舉了個例子:

我們需要寫一個函式,它能夠生成累加器,即這個函式接受一個引數n,然後返回另一個函式,後者接受引數i,然後返回n增加(increment)了i後的值。

此時


Lisp:

CL-USER> (defun foo(n)
       (lambda(i)(incf n i)))
FOO

 

Lisp的實現很簡單,但是Python的如下實現:


Python:

  def foo (n):
     return lambda i:  n + i


文中說這樣的語法是不合法的。。。。。。。於是乎,作者猜想“Python總有一天是會支援這樣的語法的”,而事實上,經我測試,現在的Python已經完全支援此語法。。。。。(修正以前文章中的錯誤,原來的測試有誤,這點也印證了程式語言進化的方向。

但是,我還是感嘆,即使學習了Python這樣現代的語言,我的腦袋中也很少有關於函式的抽象,比如上面的例子中的那種,語言決定著你的思維的方式,決不是假話,我受了太多OO的教育,以至於失去了以其他方式思考程式的能力。

屬於更為激進的一派,他認為,不良好支援函式抽象的語言根本不值的學習。(這裡的函式抽象不是指有函式,而是指類似上面的形式。)現在的語言,從Ruby,Javascript,甚至Perl 5,都已經支援類似的函式程式設計語法,同時也感嘆,能夠進化的語言,你的學習總是不會白費的^^比如總是給我驚喜的Python。


未完,待續......................

原創文章作者保留版權 轉載請註明原作者 並給出連結

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

 

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述