2016-06-08 28 views
4

也許這個問題太籠統了,不過我會嘗試: 有關於常見lisp類型的綜合指南嗎?關於常見lisp類型的綜合指南

我有點困惑這個問題:

爲什麼在make-array:element-type宣佈非原始類型都提升到t?編譯時或運行時檢查真正的聲明類型是否有可能?

爲什麼CLOS插槽定義的類型不能用作約束,允許將任何類型的值放入插槽中?再次,檢查怎麼樣?

函數的類型聲明與declare相同。它們只是編譯器的優化提示嗎?

此外,我可以使用自定義類型說明符,包括satisfies在前面提到的地方進行一些可靠的檢查,或者它們只能用於與typep e.t.c的顯式檢查?正如你所看到的,我在腦海裏有一些混亂,所以真的很感謝任何精巧的指南(或一套指南)。

我在SBCL上,但也很高興知道實現之間的差異。

+1

在這個問題中可能有太多問題,這使得這個問題有點過於寬泛。但綜合指南可能只是HyperSpec中的部分選擇。例如,對於類型聲明,您可以查看[聲明類型](http://www.lispworks.com/documentation/lw50/CLHS/Body/d_type.htm),它準確描述了類型聲明的含義(非常多如果變量的值實際上不是那種類型,那麼您有未定義的行爲)。但未定義的行爲可能是有用的,因爲編譯器可以:(i)插入類型檢查以提供安全性,並讓您知道是否... –

+0

事情出錯;或者(ii)使用*不會檢查類型的優化代碼,因爲您承諾價值將是正確的類型。它將執行的操作可能取決於其他聲明,例如任何[OPTIMIZE聲明](http://www.lispworks.com/documentation/lw50/CLHS/Body/d_optimi.htm#safety)的值。 –

+0

可能是「綜合指南」的一部分的HyperSpec的另一部分將是章節[4。類型和類](http://www.lispworks.com/documentation/lw50/CLHS/Body/04_.htm)。該部分實際上內容稍微薄弱,但鏈接到幾乎所有相關條目(例如,哪些類型被使用,哪些可以定義它們,如何使用等等)。 –

回答

4

你需要告訴編譯器,如果你想讓它實際執行類型的安全優化:

CL-USER> (declaim (optimize (safety 3))) 
NIL 
CL-USER> (defclass foobar()()) 
#<STANDARD-CLASS COMMON-LISP-USER::FOOBAR> 
CL-USER> (defun foo (a) 
      (make-array 1 :element-type 'foobar 
         :initial-contents (list a))) 
FOO 
CL-USER> (foo (make-instance 'foobar)) 
#(#<FOOBAR {1005696CE3}>) 
CL-USER> (foo 12) 
;=> ERROR 
CL-USER> (declaim (ftype (function (integer integer) integer) quux)) 
(QUUX) 
CL-USER> (defun quux (a b) 
      (+ a b)) 
QUUX 
CL-USER> (quux 12 12) 
24 (5 bits, #x18, #o30, #b11000) 
CL-USER> (quux 12 "asd") 
;=> ERROR 

檢查類型在運行時會增加一些開銷(尤其是如果它在一個循環發生) ,並且可以爲單個值完成多次,所以默認情況下不會完成。

(declaim (optimize (safety 3))) 

(defun some-predicate-p (a) 
    (format t "~&Checking type...") 
    (integerp a)) 

(deftype foo() `(satisfies some-predicate-p)) 

(defclass bar() 
    ((foo :type foo :initarg :foo))) 

(declaim (ftype (function (foo) list) qwerty)) 
(defun qwerty (foo) 
    (loop repeat 10 collecting (make-instance 'bar :foo foo))) 

(qwerty 12) 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
; Checking type... 
;=> (#<BAR {1003BCA213}> #<BAR {1003BCA263}> #<BAR {1003BCA2B3}> 
; #<BAR {1003BCA303}> #<BAR {1003BCA353}> #<BAR {1003BCA3A3}> 
; #<BAR {1003BCA3F3}> #<BAR {1003BCA443}> #<BAR {1003BCA493}> 
; #<BAR {1003BCA4E3}>) 

如果你想要一個功能隨時查詢一個地方的類型,不管優化設置,您應該手動使用CHECK-TYPE

+0

不錯!打開類型檢查測試/調試是否是一種常見的做法?並關閉生產? – leetwinski

+0

還有更多的全球性問題:在普通的lisp中使用類型是否是一種常見的做法?或者我應該像沒有類型檢查的任何語言一樣編碼,比如JS?意識形態上常見的lisp方式是什麼? – leetwinski

+0

只要不是太不方便(特別是對於基本類型),最好有類型聲明。在開發中,你可能應該使用'(declaim(optimize(debug 3)(safety 3)(speed 1)))',但是在生產中它取決於你的需求。對於一些重要的數學代碼,你可能需要'(速度3)(安全0)',而對於正常運行時間比速度更重要的網絡服務器,(安全3)(速度2)'加上適當的錯誤處理有待改善。 – jkiiski

2

在編譯期間如何處理類型由實現定義。在SBCL的情況下,類型通常被視爲斷言,但實際行爲取決於優化級別。

類型爲斷言意味着如果一個函數需要一系列n併產生一個字符串s,你一般不會假設n是一個數字。相反,你所擁有的是保證如果函數返回,那麼n實際上是一個數字而s現在是一個字符串。但是如果您重新使用s,則編譯器有機會跳過對作爲字符串的s的檢查。這通常是你想要的,因爲你的功能在全球範圍內可用,因此可以從任何地方調用。由於函數負責檢查它們的輸入,所以你總是首先檢查數字n是正常的。

然而,函數的類型聲明可以幫助您在上下文中調用函數的情況下,它可以證明類型在運行時肯定不匹配(類型爲空的交集)。爲了盲目信任類型斷言,你必須降低安全級別。

注:我最初發布它的評論,但爲了避免被刪除,這裏是代表CL類型之間的關係一個漂亮的圖形鏈接:

https://sellout.github.io/2012/03/03/common-lisp-type-hierarchy/

3

爲什麼是在make-array中聲明的非原始類型:element-type被提升爲t?編譯時或運行時檢查真正的聲明類型是否有可能?

:element-type參數是否有一個實現可以爲陣列選擇優化的內存佈局 - 主要是爲了節省內存空間。這對於基元類型通常很有用。對於其他類型,大多數Common Lisp運行時將沒有優化的存儲實現,因此聲明將沒有任何有用的效果。

爲什麼CLOS槽定義類型不能作爲約束條件,允許將任何類型的值放入槽中?再次,檢查怎麼樣?

一個實現可能會這樣做。

Clozure CL:

? (defclass foo() ((bar :type integer :initform 0 :initarg :bar))) 
#<STANDARD-CLASS FOO> 
? (make-instance 'foo :bar "baz") 
> Error: The value "baz", derived from the initarg :BAR, 
    can not be used to set the value of the slot BAR in 
    #<FOO #x302000D3EC3D>, because it is not of type INTEGER. 

同爲功能的類型聲明,聲明用..他們只是優化提示編譯器?

帶有declare的類型聲明可以忽略 - 例如在Symbolics Genera中,大多數聲明都會被忽略。實現不需要處理它們。大多數實現至少將它們解釋爲保證某些對象將是該類型的,併爲此創建優化的代碼 - 可能沒有運行時檢查和/或該類型的專用代碼。但通常需要設置相應的優化級別(速度,安全性,調試...)

此外,從CMUCL的編譯器(SBCL,...)派生的編譯器可能會將它們用於某些編譯時檢查。

但是在ANSI CL標準中沒有指定任何效果。該標準提供了聲明並將解釋留給實現。