動態語言

動態編程語言高級編程語言的一個類別,在計算機科學領域已被廣泛應用。它是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。動態語言目前非常具有活力。眾所周知的ECMAScriptJavaScript)便是一個動態語言,除此之外如PHPRubyPython等也都屬於動態語言,而CC++Java等語言則不屬於動態語言。

大部分動態語言都使用動態類型,但也有些不是。

特徵

動態語言可能包含的特徵有:eval函數、對象運行時間改變、類型推論、可變內存分配反射

Eval

一些動態語言提供eval函數。這個函數接受包含這個語言寫的代碼的一個字符串或抽象語法樹並執行它。如果這個代碼表示了一個表達式,則返回它的結果值。Erik Meijer英語Erik Meijer (computer scientist)和Peter Drayton將eval提供的運行時間代碼生成,同共享庫提供的動態裝載區別開來,並提醒注意在很多情況下eval只是用來實現高階函數(通過將函數作為字符串來傳遞)或解序列化[1]

對象運行時間改變

在動態語言中類型或對象系統典型的是可以改變的。這意味着可以從運行時間定義或基於現存類型或對象的mixin來生成新對象。這還可以用來稱謂變更繼承或類型樹,從而改變了現存類型系統表現的方式(特別是關於方法調用)。

類型推論

由於很多動態語言具有動態類型系統,基於值的內部解釋的運行時間類型推論是一種常見任務。因為值類型在各處解釋中可以變更,它經常在進行原子性運算時使用。

可變內存分配

靜態編程語言(可能間接的)要求開發者在編譯之前定義使用內存的大小(除非採用指針機制來解決問題)。一致於對象運行時間改變,動態語言隱含的需要基於程序個別運算而(重新)分配內存

反射

反射常見於很多動態語言中,典型的涉及到分析出泛型或多態數據的類型和元數據。此外它還可以包括將程序代碼作為數據的完全求值和修改,比如Lisp提供的分析S-表達式的那種特徵。

有限數目的動態語言提供一種叫做的特徵,用來組合代碼內省(檢查類、函數和關鍵字來知道它們是什麼、幹什麼、知道什麼的能力)和eval。多數當代程序員是從CC++中知道這個術語的,在這裡它們是建造入語言的小子集中的一種靜態特徵,只有在程序的文本上進行字符串替換的能力。在動態語言中,它們提供對編譯器的內部工作的訪問,和對解釋器、虛擬機或運行時系統的完全訪問,允許定義可以優化代碼或修改語言的語法或文法的類似語言的構造。

例子代碼

下列代碼使用Common Lisp和它的Common Lisp對象系統(CLOS)展示語言的動態特徵,具體採用的實現是SBCL

代碼在運行時間算得和後期綁定

這個例子展示函數可以在運行時間從源代碼進行修改:

; 源代码作为数据存储在一个变量中
* (defparameter *best-guess-formula* '(lambda (x) (* x x 2.5)))
*BEST-GUESS-FORMULA*

; 从源代码创建一个函数并在运行时间执行,这个函数可获得于best-guess名下
* (compile 'best-guess *best-guess-formula*)
BEST-GUESS
NIL
NIL

; 这个函数可以被调用
* (best-guess 10.3)
265.225

; 源代码可以在运行时间改进
* (setf *best-guess-formula* `(lambda (x) ,(list 'sqrt (third *best-guess-formula*))))
(LAMBDA (X) (SQRT (* X X 2.5)))

; 这个函数的新版本被编译
* (compile 'best-guess *best-guess-formula*)
BEST-GUESS
NIL
NIL

; 下次调用将调用新版本,这是后期绑定的特征
* (best-guess 10.3)
16.28573

對象運行時間改變

這個例子展示,現存的實例在它的類變更時可以被變更來包括一個新槽,還有現存的方法可以被替代為新版本:

; person类,person有一个name
* (defclass person () ((name :initarg :name)))
#<STANDARD-CLASS COMMON-LISP-USER::PERSON>

; 给类person的对象的一个定制的打印方法
* (defmethod print-object ((p person) stream)
    (print-unreadable-object (p stream :type t)
      (format stream "~a" (slot-value p 'name))))
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (PERSON T) {10019FFF13}>

; 一个例子person实例
* (defparameter *person-1* (make-instance 'person :name "Eva Luator"))
#<PERSON Eva Luator>

; 类person得到第二个槽,它从而拥有槽name和age
* (defclass person () ((name :initarg :name) (age :initarg :age :initform :unknown)))
#<STANDARD-CLASS COMMON-LISP-USER::PERSON>

; 更新打印这个对象的方法
* (defmethod print-object ((p person) stream)
    (print-unreadable-object (p stream :type t)
      (format stream "~a age: ~d" (slot-value p 'name) (slot-value p 'age))))
WARNING:
   redefining PRINT-OBJECT (#<STANDARD-CLASS COMMON-LISP-USER::PERSON>
                            #<SB-PCL:SYSTEM-CLASS COMMON-LISP:T>) in DEFMETHOD
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (PERSON T) {1001AB5793}>

; 现存的对象已经被变更了,它有了增加的槽和新的print方法
* *person-1*
#<PERSON Eva Luator age: UNKNOWN>

; 可以设置实例的新age槽
* (setf (slot-value *person-1* 'age) 25)
25

; 这个对象已经被更新
* *person-1*
#<PERSON Eva Luator age: 25>

基於實例的類的代碼在運行時間組裝

在下列例子中,類person得到一個新超類。print方法得到重新定義,它組裝了多個方法成為有效(effective)方法。基於實際參數的類和運行時間可獲得和適用的方法,有效方法得以組裝:

; 类person
* (defclass person () ((name :initarg :name)))
#<STANDARD-CLASS COMMON-LISP-USER::PERSON>

; person只打印它的name
* (defmethod print-object ((p person) stream)
    (print-unreadable-object (p stream :type t)
      (format stream "~a" (slot-value p 'name))))
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (PERSON T) {10019C7F13}>

; 一个person实例
* (defparameter *person-1* (make-instance 'person :name "Eva Luator"))
*PERSON-1*

; 显示这个person实例
* *person-1*
#<PERSON Eva Luator>

; 现在重新定义print方法为可扩展的
; :around方法创建print方法的上下文,并且它调用call-next-method
* (defmethod print-object :around ((p person) stream)
    (print-unreadable-object (p stream :type t)
      (call-next-method)))
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT :AROUND (PERSON T) {1001AED873}>

; 主要方法打印name
* (defmethod print-object ((p person) stream)
    (format stream "~a" (slot-value p 'name)))
WARNING:
   redefining PRINT-OBJECT (#<STANDARD-CLASS COMMON-LISP-USER::PERSON>
                            #<SB-PCL:SYSTEM-CLASS COMMON-LISP:T>) in DEFMETHOD
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (PERSON T) {1001B929A3}>

; 新类id-mixin提供了一个id
* (defclass id-mixin () ((id :initarg :id)))
#<STANDARD-CLASS COMMON-LISP-USER::ID-MIXIN>

; :after方法只打印id槽的值
* (defmethod print-object :after ((object id-mixin) stream)
    (format stream " ID: ~a" (slot-value object 'id)))
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT :AFTER (ID-MIXIN T) {1001C96D03}>

; 现在重新定义类person来包括混入的id-mixin
* (defclass person (id-mixin) ((name :initarg :name)))
#<STANDARD-CLASS COMMON-LISP-USER::PERSON>

; 现存实例*person-1*现在有了一个新槽并设置它为42
* (setf (slot-value *person-1* 'id) 42)
42

; 再次显示这个对象,print-object函数现在有一个有效方法,它调用三个方法::around方法、主要方法和:after方法
* *person-1*
#<PERSON Eva Luator ID: 42>

語言

參見

引用

  1. ^ Meijer, Erik and Peter Drayton, Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages (PDF), Microsoft Corporation, 2005 [2024-06-18], CiteSeerX 10.1.1.69.5966 , (原始內容存檔 (PDF)於2024-03-18) 
  2. ^ Archived copy. [2014-03-02]. (原始內容存檔於2014-03-02). 

延伸閱讀

外部連結