Clojure

REPL 和主入口点

clojure.main 命名空间

clojure.main 命名空间提供了一些函数,允许通过 Java 的应用程序启动器工具 java 启动 Clojure 程序和交互式会话。

clojure.main --help

clojure.main/main 入口点接受各种参数和标志。

  • 没有选项或参数,运行一个交互式的 Read-Eval-Print 循环

  • 初始化选项

    • -i, --init path 加载文件或资源

    • -e, --eval string 在字符串中执行表达式; 打印非 nil 值

    • --report target 将未捕获的异常报告给 "file"(默认)、"stderr" 或 "none",覆盖系统属性 clojure.main.report(在 1.10.1 中添加)

  • 主选项

    • -r, --repl 运行一个 repl

    • path 从文件或资源运行一个脚本

    • - 从标准输入运行一个脚本

    • -m, --main 用于执行的命名空间,用于查找 -main 函数

    • -h, -?, --help 打印此帮助消息并退出

  • 操作

    • 为通常可 set!-able 的 var 建立线程局部绑定

    • 进入 user 命名空间

    • *command-line-args* 绑定到一个字符串序列,包含在任何主选项之后出现的命令行参数

    • 按顺序运行所有初始化选项

    • 如果请求,运行 repl 或脚本

初始化选项可以重复和自由混合,但必须出现在任何主选项之前。任何求值选项在运行 repl 之前出现,会抑制通常的 repl 问候消息:“Clojure ~(clojure-version)”。

路径可以是文件系统中的绝对路径或相对路径,也可以是相对于类路径的相对路径。类路径相关的路径以 @ 或 @/ 为前缀

启动 REPL

启动 Clojure repl 的最简单方法是使用 clj 命令工具,该工具调用 clojure.main

$ clj
Clojure 1.11.2
user=>

REPL 提示符显示当前命名空间 (*ns*) 的名称,默认为 user

在使用 REPL 时,可以使用几个特殊的 var

  • *1, *2, *3 - 保持最后三个被求值的表达式的结果

  • *e - 保存最后一个异常的结果。

clojure.repl 命名空间有一些有用的函数,用于检查可用函数的源代码和文档

  • doc - 打印给定名称的 var 的 docstring

  • find-doc - 打印任何 var 的 docstring,其 doc 或名称与模式匹配

  • apropos - 返回一个匹配正则表达式的定义序列

  • source - 打印符号的源代码

  • pst - print stack trace for a given exception or *e by default

启动脚本

要将一个充满 Clojure 代码的文件作为脚本运行,将脚本的路径作为参数传递给 clojure.main

clj -M /path/to/myscript.clj

向脚本传递参数

要向脚本传递参数,在启动 clojure.main 时将它们作为进一步的参数传递

clj -M /path/to/myscript.clj arg1 arg2 arg3

这些参数将作为绑定到 var *command-line-args* 的字符串序列提供给您的程序

*command-line-args* => ("arg1" "arg2" "arg3")

错误打印

在 REPL 中

从 Clojure 1.10 开始,Clojure 错误被归类为几个阶段中的一个

  • :read-source - 在 REPL 或源文件中读取字符时抛出的错误。

  • :macro-syntax-check - 在宏调用的语法中发现的语法错误,无论是来自 spec 还是来自抛出 IllegalArgumentException、IllegalStateException 或 ExceptionInfo 的宏。

  • :macroexpansion - 在宏求值期间抛出的所有其他错误都归类为宏扩展错误。

  • :compile-syntax-check - 在编译期间捕获的语法错误。

  • :compilation - 在编译期间捕获的非语法错误。

  • :execution - 在执行时抛出的任何错误。

  • :read-eval-result - 在读取执行结果时抛出的任何错误(仅适用于读取结果的 REPL)。

  • :print-eval-result - 在打印执行结果时抛出的任何错误。

在所有阶段(除 :execution 外)抛出的异常将附带 ex-data,其中包含以下一个或多个键

  • :clojure.error/phase - 阶段指示器

  • :clojure.error/source - 文件名(无路径)

  • :clojure.error/line - 整数行号

  • :clojure.error/column - 整数列号

  • :clojure.error/symbol - 被扩展/编译/调用的符号

  • :clojure.error/class - 导致异常的类符号

  • :clojure.error/cause - 导致异常的消息

  • :clojure.error/spec - spec 错误的 explain-data

clojure.main REPL 默认包含错误的分类和打印,但此过程的各个步骤也暴露出来,供其他 REPL 使用,特别是函数

  • Throwable->map - 将异常链转换为 Clojure 数据

  • ex-triage - 分析 Clojure 异常数据,从异常链的顶部和底部提取相关信息,放入一个映射中,描述仅用于格式化异常字符串的数据集

  • ex-str - 给定一组异常数据,生成一个阶段适当的消息

clojure.main REPL 将这些函数组合成一个管道,以生成打印的异常消息:(-> ex Throwable->map clojure.main/ex-triage clojure.main/ex-str)。其他 REPL 可以根据需要使用此管道的一个或多个部分,在构建或定制其异常打印时。

作为启动器

直到 Clojure 1.10.0,clojure.main 在用作程序启动器(使用 -m、-e 或脚本)时,未捕获的异常会自动打印,以及完整的嵌套堆栈跟踪。在这种情况下,上面的错误分类和打印过程没有应用。

从 Clojure 1.10.1 开始,未捕获的异常现在将根据与 Clojure REPL 相同的错误分类和打印功能进行捕获和打印。完整的堆栈跟踪、ex-info 和其他信息将打印到由配置指定的目标。

三个可用的错误目标是

  • file - 写入一个临时文件(默认,回退到 stderr

  • stderr - 写入 stderr 流

  • none - 不写入

这些错误目标可以作为 clojure.main 的选项指定,也可以作为 Java 系统属性指定(标志优先)。在调用 clojure.main(或使用 clj 工具)时,使用 --report <target>。对于 Java 系统属性,使用 -Dclojure.main.report=<target>

其他程序可能希望利用此功能,它在 report-error 中可用,它接受一个 Throwable 和可选的 :target。

user 命名空间

默认情况下,Clojure REPL 从 user 命名空间开始,这个命名空间通常用于探索性工作。

Clojure REPL 会自动加载以下命名空间并引用以下函数

如果您切换到另一个命名空间(使用 in-nsns),这些函数将不可用,除非在那里显式引用。

加载 user.clj

Clojure 运行时将在运行时启动时查找并加载 user.clj,如果它在类路径中找到。

因为 user.clj 文件是在 Clojure 运行时初始化时加载的,所以这通常发生在应用程序中的主命名空间执行之前。因此,user.clj 加载的任何命名空间或资源都会影响应用程序的启动时间。

tap

tap 是一个共享的、全局可访问的系统,用于将一系列信息或诊断值分发到一组(可能是 effectful 的)处理程序函数。它可以用来代替更好的调试 prn,或者用于日志记录等功能。

tap> 将值发送到一组 tap。可以使用 add-tap 添加 tap,并且将使用发送到 tap> 的任何值调用它们。tap 函数可能会(短暂地)阻塞(例如,对于流),并且永远不会阻碍对 tap> 的调用,但无限期阻塞可能会导致 tap 值被丢弃。如果没有注册任何 tap,tap> 会丢弃。使用 remove-tap 移除 tap。

启动套接字服务器

Clojure 运行时现在能够在初始化时基于系统属性启动套接字服务器。此功能的一个预期用途是提供基于套接字的 REPL,但也具有许多其他潜在用途,例如在不更改代码的情况下为现有程序动态添加服务器功能。

对于每个类似于“clojure.server.<server-name>”的 JVM 系统属性,将启动一个套接字服务器。此属性的值是一个 edn 映射,它表示套接字服务器的配置,具有以下属性

  • server-daemon - 默认值为 true,套接字服务器线程不阻止退出

  • address - 主机或地址,默认为环回

  • port - 正整数,必填

  • accept - 在套接字接受时要调用的函数的命名空间符号,必填

  • args - 要传递给 accept 的参数的顺序集合

  • bind-err - 默认值为 true,将 *err* 绑定到套接字输出流

  • client-daemon - 默认值为 true,套接字客户端线程不阻止退出

此外,还提供了一个 REPL 函数,它经过稍微定制,可与 clojure.core.server/repl 中的套接字服务器一起使用。

以下是启动带有 REPL 侦听器的套接字服务器的示例。这可以添加到任何现有的 Clojure 程序中,以允许它通过对端口 5555 的本地连接接受外部 REPL 客户端。

-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"

使用 Clojure CLI,使用 -J 标志将选项传递给 JVM(注意,这也会除了套接字 REPL 之外启动一个本地 REPL)

clj -J-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"

您可以用来远程连接到此 REPL 的示例客户端是 telnet(也可以使用 netcat

$ telnet 127.0.0.1 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
user=> (println "hello")
hello

您可以通过使用特殊命令 :repl/quit 指示服务器关闭客户端 REPL 会话

user=> :repl/quit
Connection closed by foreign host.

另请参见