如何将我的GUI代码与功能分开到2个Rkt文件中?

时间:2019-10-25 07:29:32

标签: user-interface scheme lisp racket

我有一个大型程序,该程序在修改对象状态的函数与调用函数的对象实例之间进行调用。

现在,它们都在一个.rkt文件中。一些函数引用对象以更新其内容,而某些对象引用其回调字段中的函数。有没有办法将功能与GUI代码分开?

1 个答案:

答案 0 :(得分:1)

正如我在评论中提到的那样,此问题分为两部分:

  • 首先是如何使用GUI组件构造程序,或者一般而言;
  • 第二个是Racket如何为您提供第一个帮助。

我认为第一部分是一个巨大的话题:已经编写了有关该主题的书籍,并开设了课程。我绝对不愿意回答那部分。

第二部分,嗯,由于我不是一名真正的球拍专家,所以我还是不太愿意回答这个问题。但是,就像没有其他人一样,也许我可以为一些简单的案例提供帮助(我认为大多数案例都证明很简单)。

警告:肯定过分简化,几乎可以肯定包含错误。我欢迎任何比我更了解Racket模块系统的人进行纠正。答案很简单,很简单:抱歉。

球拍中的模块

要意识到的基本事情是,您在Racket中编写的所有内容都是模块定义的一部分(整个#lang ...东西是模块定义的语法糖)。

模块可以决定要导出到其他模块的名称(?),以及有关如何导出它们的一些信息,还取决于它们依赖的其他模块以及它们如何依赖它们的一些信息。 / p>

模块可以嵌套在其他模块中,包括在同一文件中。但是最简单的情况是,每个文件都包含一个模块,这就是我什至会尝试处理的全部内容。

引入了模块here,参考手册是here

定义提供的名称:provide

这是由provide完成的。这有点语法,它说明了从模块导出的名称:模块中的所有其他定义都是私有的。

provide有很多复杂性,但是假设我有一个文件定义了“用foo做某事”的概念。它想定义一个函数call-with-foo和一个宏with-foo,它们以常规方式相互关联。但是文件中还有很多其他事情与foo的实现有关,这是私有的。所以我的文件"foo.rkt"可能看起来像这样:

#lang racket

(provide call-with-foo
         with-foo)

(define (call-with-foo fn)
  ...)

(define-syntax-rule (with-foo (f) form ...)
  (call-with-foo (λ (f) form ...)))

(define (make-foo ...)
  ...)

(define (validate-foo foo)
  ...)

...

因此,这意味着任何想使用该模块的模块只能看到call-with-foowith-foo:所有其他定义都是内部的。

provide可以做的还很多:例如,它可以在导出定义时重命名它们。如果您要重新定义语言的基本部分,这将很有用。例如,如果我定义的语言有点像Racket,但是define不同,我可能会写:

#lang racket

(provide (rename-out [new-define define]))

(define-syntax new-define
  ...)

;;; This is Racket's define, not ours
;;;
(define ...)

您可以说诸如“导出所有内容”(all-defined-out)或“导出除...以外的所有内容”(except-out)之类的东西,依此类推。您可以做很多事情。

指定您所依赖的模块

因此,通常有两种方法可以从其他模块导入名称。

第一个是通过#lang ...:类似

#lang racket
...
我认为

相同
(module <name> racket ...)

<name>来自文件名,这意味着“首先使用racket模块导出的所有名称(但愿意覆盖它们)”。我认为还有更多,因为您还可以在此处重新定义文件其余部分的语法的基本方面。无论如何,#lang告诉模块应该从哪里开始。

另一种方法是通过require。它甚至比provide还要毛茸茸,因为它不仅需要能够指定诸如“我只需要此模块中的某些东西”和“我需要此模块中的某些名称下的东西”之类的东西,它还< em> 还需要能够指定“此模块”的含义。

您看到的最常见的情况是指定“ thos模块”,类似于(require racket/tcp),这意味着“我需要"tcp"集合中的"racket"模块”(这是秘密地与(require (lib "racket/tcp")相同,实际上我认为这样更容易理解),其中整个“集合”都是arcane and complicated,从某种意义上说,软件安装系统一直都是这样(尽管如此,我认为这并不是不可理解的)

但是对于您定义为程序一部分的模块,编写事情要简单得多:您可以通过(表示字符串的)文件名来指定“此模块”,该文件名相对于执行{{ 1}}。如果我想从上面的require模块导入内容,我只是说:

"foo.rkt"

现在,我已经拥有了愿意给我的一切(一切都以其(require "foo.rkt") 形式存在)。

provide一样,我可以做各种技巧来指定我想要得到的东西,以及重命名&c&c。适用于provide的{​​{1}}格式的简单情况是:

"foo.rkt"

这意味着“只要给我provide,我什么都不关心”。这很有用,因为这意味着您可以非常具体地确定所需的名称,而不会因垃圾而使模块混乱。

(require (only-in "foo.rkt" with-foo)) 可以做很多其他事情。

模块和合同

您可以执行一项非常有用的操作,以在模块边界指定合同。介绍了合同here,参考资料是here

让我们说,对于我的with-foo模块,我知道require期望将过程作为其参数,并且该过程只获得一个参数,并且可能返回任何内容。有两种方法可以执行此操作:您可以在"foo.rkt"中的函数上定义协定:

call-with-foo

或者您可以在"foo.rkt"级别指定联系人:

(define/contract (call-with-foo fn)
  (-> (-> any/c any) any))
  ...)

对于模块用户,这些基本相同。第一种情况看起来更好,因为即使在模块内也会执行合同。但是第一种情况,例如,允许您在模块边界执行合同,该合同比模块内的合同严格,这很有用。

在任何情况下,合同都是一个很不错的工具,可以尽早发现问题,并且在模块边界特别有用。

大模块

几乎不可避免地发生的一件事是,您的小型单文件模块最终变得太大,因此您希望它成为一个以上的文件。这很容易做到:您可以使您的主模块文件重新实现模块。因此,例如,provide可能变为:

(provide (contract-out
          (call-with-foo
           (-> (-> any/c any) any)))
         with-foo)

"foo.rkt"可能是:

#lang racket

(require "foo/main.rkt")

(provide (all-from-out "foo/main.rkt"))

最后,"foo/main.rkt"可能已经实现了现在庞大的模块的一部分,并适当地以#lang racket (require "simple.rkt" "complicated.rkt") (provide (all-from-out "simple.rkt" "complicated.rkt")) 形式完成了

"foo/simple.rkt"

所有provide似乎都是* nix所特有的,但是实际上这一切都是平台无关的:模块规范并不是真正的路径名,它们只是被翻译成路径名,并且翻译发生了适合平台。

#lang racket (provide (contract-out (call-with-foo (-> (-> any/c any) any)))) (define (call-with-foo fn) ...) 的原因是,如果它变成了一个库,那么(require "x/y.rkt")的意思是无论"main.rkt"告诉您去哪里,(require .../foo) '。至少我是这样认为的。)