揭示1.0:阅读Clojure的Eval可视化循环

2020-09-16 22:44:08

REPL是进入运行程序的一个很好的窗口,但其输出的文本性质限制了开发人员检查程序的能力:文本不是对象,我们处理的是VM中的对象。

Display旨在通过创建进程内REPL输出窗格来解决此问题,该窗格使检查值与选择感兴趣的数据一样简单。它识别文本的价值作为一个通用接口,这就是为什么它的输出看起来像文本:你可以选择它,复制它,把它保存到一个文件中。与文本不同,显示输出包含对打印值的引用,使检查选定值成为打开上下文菜单的问题。

与基于DataFy/NAV的工具不同,Display不强制任何给定对象的特定数据表示,使其成为一个开放的集合-这包括将DataFy/NAV作为可用选项之一。默认情况下,它不使用DataFy/NAV,因为在没有进程间通信的情况下,DataFy会丢失。

Integer并不局限于文本,它使用明智的语法突出显示来帮助区分各种对象:text、java.lang.Integer根据它是从符号生成还是从类生成,看起来会有所不同。

执行此命令将启动一个repl,并打开将镜像shell中的求值的显示输出窗口。

Clojure 1.10添加了敲击&>函数,其目的类似于打印用于调试的值,但您得到的不是字符,而是对象。显示回复在其输出窗口中显示点击的值-您将不再需要println!

您可以使用上下文菜单中的文本输入对任何选定的值计算代码。您可以编写要调用的函数的单个符号,也可以编写*v将替换为所选值的形式。

JVM中的任何对象都有类和字段,使它们易于访问以供检查是极其重要的。通过java-bean上下文操作,您可以获得VM中对象的类似调试器的视图。对此信息的访问极大地提高了虚拟机的可见性,并允许对其进行浏览。例如,对于类路径上的任何类,您都可以获得它来自的位置:

你可以打开类似URL的东西和文件:既可以在Display内部打开,也可以在外部使用操作系统中的其他应用程序打开,例如文件资源管理器、浏览器或文本编辑器。

Display可以向您显示各种运行时值的文档和源代码-名称空间、变量、符号、函数、关键字(如果它们定义了规范)。与cljdoc一样,它支持在文档字符串中使用[[wikilink]]语法来引用其他变量,从而使它们可以被访问以供进一步研究。

Display可以查看实现clojure.lang.IRef(原子、代理、变量、引用等)的任何对象,并将其显示为自动更新或作为后继者日志显示。

Display可以将特定形状的数据显示为通常可浏览的图表:当您在图表上找到感兴趣的数据点时,您可以进一步检查该数据点中的数据。

最简单的形状是标有数字的。带标签意味着这些数字存在于对每个数字都有唯一标签的某个集合中。对于映射,键就是标签;对于顺序集合,索引就是标签;对于集合,数字本身就是标签。

其他图表支持更灵活的数据形状-这既是因为它们可以显示多个数据系列,也是因为它们可以浏览,在这种情况下,使用数字附加一些元数据可能会很有用。由于JVM编号不允许元数据,因此您可以使用元组,其中第一项是数字,第二项是元数据。条形图可以显示已标记的数字(单个数据系列)或本身已标记的已标记数字(多数据系列):

折线图对于显示进度很有用,因此建议它们显示序号(以及标记为序号的序号):

最后,Display具有散点图,可在二维平面上显示坐标。坐标表示为2个数字的元组,与数字一样,您可以使用坐标的元组和任意值来代替坐标。Display将为坐标集合和已标记的坐标集合建议散点图。

在某些情况下,更好地理解由表表示的值:同类项的集合,其中列使比较这些项的相应部分变得更容易;大型深度嵌套数据结构(更容易逐层查看它们)。

Display被设计成具有良好的性能:它可以非常快速地流式传输语法突出显示的输出。除此之外,还有各种数据检查帮手:

LambdaIsland开箱即用的深度差异输出突出显示:您所需要做的就是将其放在类路径中;

具有一个或多个选项卡的结果面板,其中包含从上下文菜单生成的操作结果。

可以使用包含UI首选项的EDN映射的vlaaad.infoal.prefs java属性来自定义Display。支持的密钥(全部可选):

:FONT-FAMILY-SYSTEM字体名称(如";Consolas&34;)或url(如";FILE:/Path/to/font.ttf";或";https://ff.static.1001fonts.net/u/b/ubuntu.mono.ttf";)-Display仅支持等宽字体;

要揭示的主要入口点是vlaaad.reposure ns,它具有各种响应和较低级别的数据检查功能。

它是一个repl包装clojure.main/repl,另外还支持:repl/Quit和TAP>;。事情就是这么简单。我一直在用它。示例:

这个prepl的工作方式类似于clojure.core.server/io-prepl。它的用途是在您的计算机上的进程中运行,您希望使用另一个支持prepl的工具连接到该进程。示例:

现在,您可以使用任何套接字REPL连接到此进程,它将为每个连接显示一个显示窗口:

$NC localhost 5555#显示窗口(+1 2 3)#Input{:Tag:RET,:val";6";,:NS";User";,:ms 1,:Form";(+1 2 3)";}#输出。

当它在发生开发的流程中运行时,Display是最有用的。这个prepl与前两个不同,它不是这样的:它连接到一个远程进程,并显示一个窗口,显示从网络到达的值。它不能从轻松访问打印的引用中获益,因为这些引用指向从字节反序列化的值,而不是目标VM中的值。它仍然很好且性能很高,当您想要使用Display与另一个在类路径上没有Display进程(例如Production或ClojureScript prepl)对话时,它很有用。

使用Display:$clj-A:display-m vlaaad.show remote-prepl:port 50505#连接到预置1此时,会发生两件事:#1.带有cljs prepl浏览器窗口出现#2.显示窗口打开#inputjs/window#output{:tag:ret,:val#Object[Window[Object Window]],:NS";cljs.user";:ms 25,:Form";js/Window";

调用此函数将创建并显示一个显示窗口。它返回一个您可以向其提交值的函数-这些值将显示在“输出”面板中。所有内置的可视响应都是将值提交到此泛型函数创建的窗口的其他响应的薄包装。您可以使用它来创建自定义的显示风格的响应,或者,您可以将其配置为仅显示分接值,而不是将其用作REPEL。

(Required';[vlaaad.Display:As Display]);;打开将显示点击的值的窗口:(Add-Tap(Display/UI));;将值提交到窗口:(点击&>;{:Will-I-See-this-in-Display-Window?True})。

使用User API,您应该能够将编辑器配置为使用Display,但仍有一些要点值得讨论。

对于草书,您应该使用“clojure.main”repl类型创建“local repl”运行配置。对于首选项,请使用“JVM args”输入,但请注意,它会在空格中拆分参数,因此您应该使用逗号,例如-Dvlaaad.openal.prefs={:Theme,:light}。这是最简单的设置,它允许IDEA启动您的应用程序并建立用于发送表单的REPL连接。

有时这种设置并不理想:您可能希望使用其他方式启动应用程序,然后使用IDEA连接到它。在这种情况下,您不应该使用“remote repl”运行配置,因为它会将您的表单和结果重写为不可读的内容。相反,您仍然应该使用“local repl”运行配置,该配置使用连接到您的进程的远程repl客户端。示例:

使用“clojure.main”repl类型创建“local repl”运行配置,使用remote-repl别名将其设置为“run with Dep”,并在参数中指定-m vlaaad.remote-repl:端口5555。

对于需要nrepl display的开发工作流具有一个中间件,该中间件将显示由nrepl:vlaaad.openal.nrepl/midleware生成的评估结果,您需要将其添加到您的中间件列表中。Nrepl的最低要求版本为0.6.0。

或者,您可以在项目目录中创建.nrepl.edn文件,该文件将由nrepl拾取。示例.nrepl.edn文件:

有3种方式可以扩展显示以满足您的需要:自定义格式化程序、操作和视图。所有这三个文件都在vlaaad.openal.ext名称空间中提供(在下面的示例中别名为rx)。

它们都共享的一个功能是注释-在显示状态下与您的对象一起存在的非侵入性元数据。与基于DataFy/NAV的工具不同,它不会阻碍您的对象,使Clojure的元数据与您的程序中的完全相同,而且,由于批注位于对象旁边,Display允许对任何对象进行批注--不仅仅是IMetas。

“格式”定义值在“输出”面板中的显示方式。格式化程序分派是一个多方法,它查看:vlaaad.infoal.stream/type元关键字,如果没有,则在Object的类中查找。推荐的扩展此多方法的方式是使用(destream Dispatch-val[x ann?]。Sf)用正在格式化的值自动标记格式化区域的宏。有一小部分函数以一种有效的方式执行格式化的流式处理,称为流式函数(简称SFS)。

它们通常用在非流式正文中,以配置内容作为文本的外观。这样的FS不会使用可供检查的值来标记它们发出的文本,而是依赖它们的上下文(例如,逆流)来标记它们发出的内容。它们只有5个:

(原始字符串x样式?)。AND(转义字符串x样式?逃逸-FN逃逸风格?)。发出语法突出显示的文本。两者都接受支持以下键的样式映射::Fill-Text Fill Color,可以是字符串(如";#ff0000";)、Web颜色关键字(如:Red),也可以是定义主题相关颜色的特殊值之一::Object,用于表示复合对象,这些复合对象通常打印一些其他值作为其文本表示的一部分;

(水平SF*)和(垂直SF*)包装可变数量的SF并对齐它们,例如,您可以将地图流式传输为水平{,条目和},其中条目中的每个条目垂直对齐;

分隔符在视觉上分隔发出的窗体,在水平块中它是一个不可选择的空间,在垂直块中它是一条空线;

这些FS允许您使用其他值的默认流式处理来流式处理其他值。这也是批注流量值的位置。

(STREAM x ANN?)。为传递的值发出格式化-这是格式化过程的核心;

(横向Xs Ann?)。和(垂直Xs ann?)。处理集合。与低级科幻同行的不同之处在于,他们在流媒体之前没有意识到整个收藏。你可以很容易地(垂直地(范围))做,而且它不会阻碍流媒体的过程;

(物品Xs Ann?)。猜测格式:根据输入的不同,可能表现为水平方向或垂直方向。可以在流式传输之前实现整个集合;

注释只有在使用时才有用,并且它们是从操作中使用的。这里有一个使用注释配置格式的示例,并使用这些注释进行强大的数据检查。

(作为xann?Sf)允许对某些值x使用非缺省流函数,同时使show:value操作可用来查看该值的默认格式。这可能有用的一个示例是显示身份散列代码,该代码通常具有不同的int表示来表示其含义:(Defn Identity-Hash-code-SF[x](let[hash(system/IdentityHashCode x)](rx/as hash(rx/raw-string(format";0x%x";hash){:Fill:scalar})。

(STREAM-AS-IS SF)使SF值本身成为可流动的。流函数是使用解压缩的类名作为格式化表示的普通函数,当SF被提交以显示时,它也将使用这种默认格式。在某些情况下,您可能只想流式传输一些格式化的表单来显示(例如,自定义操作的结果),这就是实现这一点的方法。

(覆盖样式SF f args*)变换另一个SF的文本样式,这在您可能想要以不同方式标记整个对象及其组成部分的情况下非常有用(例如,将语义上“忽略”的对象设置为灰色)。

如果在Display UI中选择的文本具有关联值,则在其上请求上下文菜单将显示一个弹出窗口,该弹出窗口检查所有注册的操作并建议适用的操作。使用(解除ID[x ann?]。Body*)宏来注册新操作。

Action Body应返回0-arg函数,以指示该操作可用:当在弹出的上下文菜单中选择该操作时,将执行该函数。忽略任何其他结果,包括引发的异常。动作主体应该合理快速(例如,不执行磁盘IO),因为当用户请求弹出窗口时,所有动作都会被检查。另一方面,返回的函数可以在需要的时间内阻塞:Display将在执行时显示加载指示器。

显示字符串如何未转义的最小操作示例(例如,在单独的行上显示hello和world):

如前所述,这里有一个更大的示例,它展示了操作和格式化如何相互构建以帮助数据探索:

Output面板和Results面板之间的主要区别在于,后者可以显示Display的UI框架(JavaFX)允许的任何图形节点。Display构建在cljfx之上--受Reaction启发,JavaFX的声明性、函数性和可扩展包装器。Display使用vlaaad.infoal.view/viewable协议将所有操作结果转换为cljfx组件描述,该协议默认使用输出面板视图显示它们。您可以使用(view-as-is desc)函数将协议实例具体化。

要学习cljfx/JavaFX,您应该阅读cljfx自述文件和示例以熟悉语义,并探索JavaFX javadoc以查找可用的视图。这可能是一项艰巨的任务,所以这里有个简短的介绍来感受一下。

为了描述节点,cljfx使用具有定义节点类型的特殊键-:fx/type-的映射,而其他键定义该节点的属性。Value on:fx/type key可以是关键字(采用烤肉串大小写的JavaFX类名)或函数(接收道具映射并返回另一个描述)。一些最常用的描述示例:

;显示文本{:fx/type:label:text(str(Range 10))};;显示带有回调的按钮:{:fx/type:button:text";Deploy";:on-action(fn[Event](Deploy-to-Production!))};将视图组合在一起{:fx/type:v-box;;垂直方向:孩子[{:fx/type rx/value-view;内置组件:Value MSFT-STOCK}{:FX/TYPE:H-BOX;;水平:子项[{:FX/TYPE:BUTTON:Text";Sell";:On-action(FN[_](Sell!:MSFT))}{:FX/TYPE:BUTTON:Text";Buy";:On-action(fn[_](Buy!:msft))}]}]}。

虽然cljfx支持使用map来定义回调,但您应该只使用函数-map事件处理的行为是一个可能会更改的实现细节。

Value-view是输出面板中使用的默认视图,它使用流格式显示值,例如:

Watch:All and Watch:最新动作由ref-watch-all-view和ref-watch-last-view提供支持。此外,还有(可观察的REF FN)实用程序函数,它允许通过转换查看REF-它旨在与这些视图一起使用,例如:

Observable-view允许从IREF状态派生整个cljfx组件,并在REF发生变化时实时显示其更新。有一个示例展示了如何使用它为集成管理的应用程序状态创建实时监控和控件。

表视图显示一个表。与view:table操作不同,它不会猜测列,而需要您自己提供它们,例如:{:fx/type rx/table-view:Items[:foo:foo/bar:foo/bar/baz:+]:column[{:fn名称空间}{:fn名称}{:fn#(Resolve(Symbol%)):Header';Resolve}]}。

图表视图:饼图视图、条形图视图、折线图视图和散点图视图。它们不会尝试以与其相应操作相同的方式猜测数据的形状,例如,即使只有一个数据系列,折线图数据序列也必须始终标记:

奇特的可视化效果不一定非得是您只能查看的叶节点--在绘图上选择一个数据点并将其作为一个值进行浏览不是很好吗?Display支持对图表和表格等开箱即用的内置视图进行持续的数据探索。除此之外,它还提供了一种方法,可以使用特殊的组件弹出视图在任何JavaFX节点上安装操作弹出窗口:

此描述显示您可以请求上下文菜单的标签,其弹出窗口将建议clojure.core ns上的acons。下面是一个更大的示例,说明如何为国际象棋服务器创建自定义视图,该视图将活动游戏显示为国际象棋棋盘,并允许检查任何棋子:

如果repl是一个正在运行的程序的窗口,那么Display就是一扇敞开的大门--欢迎您进来。我从检查我看到的任何物体的能力中获得了很大的影响力,我希望你也会发现揭示是有用的。