动态语言

动态编程语言高级编程语言的一个类别,在计算机科学领域已被广泛应用。它是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。众所周知的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). 

延伸阅读

外部链接