我正在考虑使用clojure,并且在尝试确定与这种常见python习语相当的clojure(和/或Lisp)方面遇到了一些麻烦。
习惯用法是在python模块的底部经常有一些测试代码,然后是运行代码的语句,例如:
# mymodule.py
class MyClass(object):
"""Main logic / code for the library lives here"""
pass
def _runTests():
# Code which tests various aspects of MyClass...
mc = MyClass() # etc...
assert 2 + 2 == 4
if __name__ == '__main__': _runTests()
这对简单的临时测试很有用。通常可以通过编写from mymodule import MyClass
来使用此模块,在这种情况下,永远不会调用_runTests()
,但最后使用代码段,也可以通过直接从命令键入python mymodule.py
来运行它线。
Clojure(和/或普通的lisp)中是否存在等价的习惯用法?我不是在一个完整的单元测试库之后(好吧,我,但不是在这个问题中),我只想在一个模块中包含一些代码,这些代码只能在某些情况下运行,所以我可以拥有一种快速运行代码的方法我一直在努力,但仍允许我的文件像普通的模块/命名空间一样导入。
答案 0 :(得分:27)
从命令行反复运行Clojure脚本并不是惯用的。 REPL是一个更好的命令行。 Clojure是一个Lisp,通常会激活Clojure并使相同的实例永远运行,并与之交互而不是重新启动它。您可以一次更改正在运行的实例中的函数,运行它们并根据需要进行戳。逃避繁琐而缓慢的传统编辑/编译/调试周期是Lisps的一大特色。
您可以轻松编写函数来执行运行单元测试等操作,只需在运行它们时从REPL调用这些函数,否则忽略它们。在Clojure中使用clojure.contrib.test-is
,将测试函数添加到命名空间,然后使用clojure.contrib.test-is/run-tests
来运行它们是很常见的。
不从命令行运行Clojure的另一个好理由是JVM的启动时间可能过高。
如果您真的想从命令行运行Clojure脚本,可以通过多种方式执行此操作。有关讨论,请参阅the Clojure mailing list。
一种方法是测试是否存在命令行参数。在当前目录中给出foo.clj
:
(ns foo)
(defn hello [x] (println "Hello," x))
(if *command-line-args*
(hello "command line")
(hello "REPL"))
根据你如何开始Clojure,你会得到不同的行为。
$ java -cp ~/path/to/clojure.jar:. clojure.main foo.clj --
Hello, command line
$ java -cp ~/path/to/clojure.jar:. clojure.main
Clojure 1.1.0-alpha-SNAPSHOT
user=> (use 'foo)
Hello, REPL
nil
user=>
如果您想了解这是如何运作的,请参阅Clojure来源中的src/clj/clojure/main.clj
。
另一种方法是将代码编译为.class
文件并从Java命令行调用它们。给定源文件foo.clj
:
(ns foo
(:gen-class))
(defn hello [x] (println "Hello," x))
(defn -main [] (hello "command line"))
创建一个目录来存储已编译的.class
文件;默认为./classes
。你必须自己创建这个文件夹,Clojure不会创建它。另外,请确保将$CLASSPATH
设置为包含./classes
以及包含源代码的目录;我假设foo.clj
在当前目录中。所以从命令行:
$ mkdir classes
$ java -cp ~/path/to/clojure.jar:./classes:. clojure.main
Clojure 1.1.0-alpha-SNAPSHOT
user=> (compile 'foo)
foo
在classes
目录中,您现在将拥有一堆.class
个文件。要从命令行调用代码(默认情况下运行-main
函数):
$ java -cp ~/path/to/clojure.jar:./classes foo
Hello, command line.
有很多关于在clojure.org上编译Clojure代码的信息。
答案 1 :(得分:1)
我对Clojure很新,但我认为Clojure小组的this discussion可能是一个解决方案和/或解决方法,特别是Stuart Sierra在4月17日晚上10:40的帖子。
答案 2 :(得分:1)
在Common Lisp中,您可以使用features条件阅读。
#+testing (run-test 'is-answer-equal-42)
如果绑定到cl:*features*的要素列表包含符号:testing。
,则只会读取上面的内容,从而在加载期间执行。例如
(let ((*features* (cons :testing *features*)))
(load "/foo/bar/my-answerlib.lisp"))
将临时添加:测试到功能列表。
您可以定义自己的功能并控制Common Lisp系统读取哪些表达式以及跳过哪些表达式。
此外,您还可以:
#-testing (print '|we are in production mode|)
答案 3 :(得分:1)
http://rosettacode.org/wiki/Scripted_Main#Clojure还有一系列不同的可能性。 (如果你找到一个新的 - 请加上它。; - ))
答案 4 :(得分:0)
您可能想要查看来自clojure-contrib的test-is库。它不是同一个成语,但它应该支持一个非常相似的工作流程。
答案 5 :(得分:0)
Common Lisp和Clojure(以及其他lisps)提供了REPL的交互式环境,你不需要像«if __name__ == '__main__'
»这样的技巧。 python有类似REPL的环境:来自命令行的python,用于Emacs的ipython,python模式等。
你应该创建库,为它添加一个测试套件(Common Lisp有很多测试框架;我更喜欢5am框架,有一个可用的框架调查here)。然后加载库,在REPL中你可以对库做任何事情:运行测试,调用函数,实验等。
当您发现测试失败时,您需要对其进行修复,重新编译已更改的代码,然后继续尝试,运行测试而不重新启动整个应用程序。这节省了大量时间,因为正在运行的应用程序可能已经积累了很多状态(它可能已经创建了gui窗口,连接到数据库,达到了一些不易重现的关键时刻),并且您不必重新启动它每次改变之后。
以下是Common Lisp的示例(来自我的cl-sqlite库):
代码:
(def-suite sqlite-suite)
(defun run-all-tests ()
(run! 'sqlite-suite));'
(in-suite sqlite-suite)
(test test-connect
(with-open-database (db ":memory:")))
(test test-disconnect-with-statements
(finishes
(with-open-database (db ":memory:")
(prepare-statement db "create table users (id integer primary key, user_name text not null, age integer null)"))))
...
和互动环节:
CL-USER> (sqlite-tests:run-all-tests)
.......
Did 7 checks.
Pass: 7 (100%)
Skip: 0 ( 0%)
Fail: 0 ( 0%)
NIL
CL-USER> (defvar *db* (sqlite:connect ":memory:"))
*DB*
CL-USER> (sqlite:execute-non-query *db* "create table t1 (field text not null)")
; No value
CL-USER> (sqlite:execute-non-query *db* "insert into t1 (field) values (?)" "hello")
; No value
CL-USER> (sqlite:execute-to-list *db* "select * from t1")
(("hello"))
CL-USER>
现在假设我在sqlite中发现了bug:execute-to-list。我转到这个函数的代码,修复bug并重新编译这个函数。然后我调用固定功能并确保它工作。内存数据库没有消失,它具有与重新编译之前相同的状态。
答案 6 :(得分:0)
Boot是一个构建工具(leiningen的替代品),supports scripts。因此,您可以使用#!/usr/bin/env boot
开头的启动脚本,该脚本可以使用-main
方法。
您还可以从命令行调用任务来调用代码的不同函数。你可以有一个打包任务,可以为其中一个函数创建一个uberjar作为入口点。
答案 7 :(得分:-3)
如果你正在谈论有一个“切入点”,你当然可以这样做:
(ns foo)
(defn foo [n]
(inc n))
(defn main []
(println "working")
(println "Foo has ran:" (foo 1)))
(main)
现在会发生的是,只要这段代码是(load-file“foo.clj”)'d或(使用'foo)或(require'foo),那么(main)将被调用,这通常不会完成。
更常见的是,可以在REPL中加载代码文件,然后用户将调用main函数。