65.9K
CodeProject 正在变化。 阅读更多。
Home

Theme-D - 使用静态类型扩展 Scheme

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2024年9月17日

CPOL

6分钟阅读

viewsIcon

2243

Theme-D 编程语言通过静态类型扩展 Scheme

对象、类型和类

Theme-D 变量的所有值都是对象。每个对象的运行时类型都是一个类。内置类包括 <object><class><integer><real><boolean><null><symbol><string><character><keyword>。用户定义的类声明了该类实例(即运行时类为该类的对象)包含的字段。类 <object><class> 是可继承的,但其他内置类则不可。每个类都有一个直接的超类。所有类都直接或间接继承自 <object>。一个类可以被声明为不可变的,这意味着在对象创建后,该类实例的字段值不能被更改。如果一个类被声明为值相等,那么该类实例的相等性是通过检查实例字段的相等性来计算的。如果一个类不是值相等的,那么该类实例只有在它们是同一个对象时才相等。一个类可以声明一个零值。此功能主要用于数值类。

以下是一个有理数类的示例

  (define-class <rational>
    (attributes immutable equal-by-value)
    (inheritance-access hidden)
    (fields
      (i-numer <integer> public hidden)
      (i-denom <integer> public hidden))
    (zero (make <rational> 0 1)))

类型是比类更一般的概念。有两种类型的类型:类和联合体。如果对象的运行时类型继承自联合体的某个组件类型,则该对象是该联合体的实例。变量的声明类型称为其静态类型。变量的运行时类型始终是其静态类型的子类型。以下是一个实现整数列表的联合类型示例

  (declare <my-list> :union)

  (define <my-list>
    (:union (:pair <integer> <my-list>) <null>))

Theme-D 为每个非抽象类生成一个构造函数。构造函数是生成类实例的过程。构造函数的参数在类的构造子句中声明。这些参数可用于计算类实例字段的初始值。构造函数的访问权限可以在 constructor-access 子句中声明。如果访问权限是 public,则构造函数可以在代码中的任何地方调用。如果访问权限是 module,则构造函数只能在声明该类的模块中调用。如果访问权限是 hidden,则该类是抽象的,无法直接构造该类的实例。以下是定义构造函数的类的示例

  (define-class <rectangle>
    (construct ((_x1 <integer>) (_y1 <integer>)
                (_x2 <integer>) (_y2 <integer>)))
    (fields
      (x1 <integer> public module _x1)
      (y1 <integer> public module _y1)
      (x2 <integer> public module _x2)
      (y2 <integer> public module _y2)
      (width <integer> public module (+ (- _x2 _x1) 1))
      (height <integer> public module (+ (- _y2 _y1) 1))))

常量

Theme-D 有两种变量:常量和可变变量。常量的值在创建后不能被更改。

程序和模块

Theme-D 中的所有代码都组织在单元中。单元可以是程序、接口或体。程序可以是普通程序或脚本。接口和实现该接口的体组合称为模块。

接口包含模块导出的所有变量的定义或声明。接口仅包含模块导出的过程和参数化过程的声明。体包含模块所有私有变量的定义以及接口中声明的所有过程和参数化过程的定义。接口和体都可以使用关键字 import 导入其他模块。接口可以重新导出从其他模块导入的变量。接口还可以使用关键字 import-and-reexport 导出另一个模块。

我们以实现有理数的模块为例。这是接口

(define-interface rational

  (import (standard-library core))

  (define-class <rational>
    (fields
      (numerator <integer> public module)
      (denumerator <integer> public module)))

  (declare rational+ (:simple-proc
    (<rational> <rational>) <rational> pure))
                  
  (declare rational- (:simple-proc
    (<rational> <rational>) <rational> pure)))

这是体

  (define-body rational

  (define-simple-proc rational+
      (((rat1 <rational>) (rat2 <rational>))
       <rational> pure)
    (make <rational>
      (+ (* (field-ref rat1 'numerator) (field-ref rat2 'denominator))
         (* (field-ref rat1 'denominator) (field-ref rat2 'numerator)))
      (* (field-ref rat1 'denominator) (field-ref rat2 'denominator))))
                  
  (define-simple-proc rational-
      (((rat1 <rational>) (rat2 <rational>))
       <rational> pure)
    (make <rational>
      (- (* (field-ref rat1 'numerator) (field-ref rat2 'denominator))
         (* (field-ref rat1 'denominator) (field-ref rat2 'numerator)))
      (* (field-ref rat1 'denominator) (field-ref rat2 'denominator)))))

泛型过程

泛型过程是一组简单过程或参数化过程(称为方法)。泛型过程可以是普通泛型过程或虚拟泛型过程。普通泛型过程是词法作用域的,而虚拟泛型过程是动态作用域的。当调用泛型过程并传入参数列表时,Theme-D 首先检查泛型过程的哪个方法可以与该参数列表匹配,即参数列表的类型是方法参数列表类型的子类型。然后,Theme-D 找出哪个匹配的方法是最佳匹配。如果找不到唯一的最佳匹配,则会引发异常。对于普通泛型过程,这些检查在编译时进行;对于虚拟泛型过程,在运行时进行。在某些情况下,可以通过静态分派来优化虚拟泛型过程的分派。

假设一个虚拟泛型过程有两个不同的方法,分别具有参数列表类型 A 和 B,以及结果类型 R 和 S。如果 A 是 B 的子类型,则 R 必须是 S 的子类型。这就是所谓的协变类型规则。协变类型规则允许 Theme-D 在编译时推断出虚拟泛型过程调用的结果类型的超类型。

考虑以下类

  (define-class <object-with-id>
    (fields
      (str-id <string> public module)))

  (define-class <widget>
    (superclass <object-with-id>)
    (fields
      (i-x <integer> public module)
      (i-y <integer> public module)))

以及以下代码

  (define-simple-proc myproc (((x <object-with-id>))
                              <none> nonpure)
    (my-print x)
    (console-newline))

  (define-main-proc (() <none> nonpure)
    (let ((x1 (make <widget> 10 20)))
      (myproc x1)))

如果我们定义 my-print 为一个普通泛型过程

  (define-simple-method my-print
      (((x <object-with-id>)) <none> nonpure)
    (console-display-line (field-ref x 'str-id)))

  (define-simple-method my-print
      (((x <widget>)) <none> nonpure)
    (console-display-line (field-ref x 'str-id))
    (console-display-line (field-ref x 'i-x))
    (console-display-line (field-ref x 'i-y)))

调用泛型过程 my-print 会调用第一个方法。

如果我们定义 my-print 为一个虚拟泛型过程

  (define-simple-virtual-method my-print
      (((x <object-with-id>)) <none> nonpure)
    (console-display-line (field-ref x 'str-id)))

  (define-simple-virtual-method my-print
      (((x <widget>)) <none> nonpure)
    (console-display-line (field-ref x 'str-id))
    (console-display-line (field-ref x 'i-x))
    (console-display-line (field-ref x 'i-y)))  

调用泛型过程 my-print 会调用第二个方法。

参数化类、类型和过程

Theme-D 允许声明具有类型参数的类、类型和过程。以下是一个复数类的示例,该类将实部和虚部的数值类型作为参数

  (define-param-class :my-complex
    (parameters %number)
    (attributes immutable equal-by-value)
    (inheritance-access hidden)
    (zero (make (:my-complex %number) (zero %number) (zero %number)))
    (fields
      (re %number public hidden)
      (im %number public hidden)))

这是一个将组件类型作为参数的列表类型

  (define-param-logical-type :my-list (%type)
    (:union (:pair %type (:my-list %type)) <null>))

请注意,Theme-D 包含 :uniform-list 作为内置类型。

这是一个遍历列表并以参数和结果列表组件类型作为参数的过程(方法)

  (define-param-method map1
      (%argtype %result-type)
      (((proc (:procedure (%argtype) %result-type pure))
        (lst (:uniform-list %argtype)))
       (:uniform-list %result-type)
       pure (optimize-proc-arg proc))
    (match-type-strong lst
      ((<null>) null)
      ((lst1 (:nonempty-uniform-list %argtype))
       (cons (proc (car lst1))
             (map1 proc (cdr lst1))))))

异常

Theme-D 支持异常来处理错误和异常情况。以下是一个示例

  (define-main-proc (((l-args (:uniform-list <string>)))
      <none> nonpure)
    (guard-without-result
      (exc
        ((rte-exception? exc)
         (case (get-rte-exception-kind (cast <condition> exc))
           ((uniform-list-ref:index-out-of-range)
            (console-display-line "Not enough arguments"))
           ((io-error)
            (console-display-line "Error opening file"))
           (else (raise exc))))
        (else (raise exc)))
      (let* ((str-filename (uniform-list-ref l-args 1))
             (fl (open-input-file str-filename))
             (str-contents (read-string fl)))
        (close-input-port fl)
        (console-display-line str-contents)
        (console-display-line "Success."))))

签名

签名是 Theme-D 的一个实验性功能。它们类似于 Java 签名,但 Theme-D 签名是多重分派的。

以下是签名 <stack> 的定义以及实现该签名的类 <a>

  (define-virtual-gen-proc push)
  (define-virtual-gen-proc pop)

  (define-signature <stack> ()
    (push (this <integer>) <none> nonpure)
    (pop (this) <integer> nonpure))

  (define-class <a>
    (fields
      (l-contents (:uniform-list <integer>) public module null)))

  (define-simple-virtual-method push
      (((store <a>) (item <integer>)) <none> nonpure)
    (field-set! store 'l-contents
                (cons item (field-ref store 'l-contents))))

  (define-simple-virtual-method pop
      (((store <a>)) <integer> nonpure)
    (let* ((l1 (cast (:nonempty-uniform-list <integer>)
                     (field-ref store 'l-contents)))
           (result (car l1)))
      (field-set! store 'l-contents (cdr l1))
      result))

Theme-D 也支持参数化签名。以下是先前示例的修改版本,其中堆栈元素类型被作为参数

  (define-virtual-gen-proc push)
  (define-virtual-gen-proc pop)

  (define-param-signature :stack (%element) ()
    (push (this %element) <none> nonpure)
    (pop (this) %element nonpure))

  (define-param-class :a
    (parameters %element)
    (fields
      (l-contents (:uniform-list %element) public module null)))

  (define-param-virtual-method push (%element)
      (((store (:a %element)) (item %element)) <none> nonpure)
    (field-set! store 'l-contents
                (cons item (field-ref store 'l-contents))))

  (define-param-virtual-method pop (%element)
      (((store (:a %element))) %element nonpure)
    (let* ((l1 (cast (:nonempty-uniform-list %element)
                     (field-ref store 'l-contents)))
           (result (car l1)))
      (field-set! store 'l-contents (cdr l1))
      result))

数值塔

Theme-D 支持类似于 Scheme 中数值塔的数值塔。该塔由以下类组成

  • <integer>
  • <rational>
  • <real>
  • <complex>

<integer><real> 是语言的内置类型,而类 <rational><complex> 由标准库实现。

每个整数都可以转换为有理数,使得这些数字可以与谓词 = 相等。类似地,每个实数都可以转换为复数,并且这些数字与 = 相等。每个有理数都可以转换为实数,但有理数和实数可能不与 = 相等。

GObject 内省支持

软件包 Theme-D-Intr 使得在 Theme-D 程序中使用支持 GObject 内省功能库成为可能。特别是,可以使用 GTK 库编写 GUI 程序。使用 Theme-D-Intr 的每个程序都必须定义一个内省文件,其中声明了导入的类(以及不是方法的函数)。还可以覆盖内省文件中的方法定义。

这是使用 Theme-D-Intr 编写的“Hello world”程序

(define-proper-program hello

  (import (standard-library core)
          (standard-library list-utilities)
          _intr-imports)

  (define-simple-method activate-my-app
      (((args (rest <object>))) <object> nonpure)
    (let* ((app (cast <gtk-application> (uniform-list-ref args 0)))
           (window (cast <gtk-window> (gtk-application-window-new app)))
           (button (cast <gtk-button>
                         (gtk-button-new-with-label "Hello, World!"))))
      (gtk-widget-set-width-request window 400)
      (gtk-widget-set-height-request window 300)
      (gtk-window-set-child window button)
      (gtype-instance-signal-connect
       button 'clicked
       (lambda (((args2 (rest <object>))) <object> nonpure)
         (g-application-quit app)
         null))
      (gtk-window-present window)
      (gtk-widget-show button)
      #f))
    
  (define-main-proc (() <none> nonpure)
    (let* ((str-app-name "org.tohoyn.hello")
           (app (gtk-application-new str-app-name null)))
      (gtype-instance-signal-connect
       app 'activate activate-my-app)
      (let ((status (g-application-run app null)))
        status))))

这是相应的内省文件

  (intr-entities
    (version Gtk "4.0")
    (classes
      (Gtk Widget)
      (Gtk Application)
      (Gtk ApplicationWindow)
      (Gtk Window)
      (Gtk Button)))

关注点

自举环境

Theme-D 包包含自举编译器和链接器。出于某些原因,它们比 Scheme 中相应的实现慢。

类型匹配优化

请参阅 本节 中的过程 map1。我们知道 lst 的类型是 (:union <null> (:nonempty-uniform-list %argtype)) 的子类型。因此,在 match-type-strong 表达式中,我们只需要检查 lst 是否为 null

更多信息

Theme-D 主页位于 此处,Theme-D-Intr 主页位于 此处

© . All rights reserved.