Подтвердить что ты не робот

Что такое clojure эквивалент идиомы Python ", если __name__ == '__main__'"?

Я работаю в clojure, и у меня есть небольшая проблема с попыткой определить эквивалент clojure (и/или Lisp) этой общей идиомы python.

Идиома заключается в том, что в нижней части модуля 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)? Я не после полномасштабной библиотеки тестирования модулей (ну, я, но не в этом вопросе), я просто хотел бы включить некоторый код в модуль, который будет запускаться только при некоторых обстоятельствах, поэтому я могу быстрый способ запустить код, над которым я работал, но все же разрешить импортировать мой файл, как обычный модуль/пространство имен.

4b9b3361

Ответ 1

Не так уж идиоматично запускать сценарии Clojure снова и снова из командной строки. REPL - лучшая командная строка. Clojure является Lisp, он обычно запускает Clojure и оставляет тот же экземпляр, который работает навсегда, и взаимодействует с ним, а не перезапускает его. Вы можете изменять функции в исполняемом экземпляре по одному, запускать их и выталкивать их по мере необходимости. Исключение утомительного и медленного традиционного цикла редактирования/компиляции/отладки - отличная особенность Lisps.

Вы можете легко писать функции, чтобы делать такие вещи, как выполнять модульные тесты, и просто вызывать эти функции из REPL, когда вы хотите их запустить, и игнорировать их в противном случае. В Clojure обычно используется clojure.contrib.test-is, добавьте свои тестовые функции в пространство имен, затем используйте clojure.contrib.test-is/run-tests, чтобы запустить их все.

Еще одна хорошая причина не запускать Clojure из командной строки в том, что время запуска JVM может быть запретительным.

Если вы действительно хотите запустить Clojure script из командной строки, существует множество способов сделать это. См. список рассылки Clojure для обсуждения.

Один из способов - проверить наличие аргументов командной строки. Учитывая это 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=>

См. src/clj/clojure/main.clj в источнике Clojure, если вы хотите посмотреть, как это работает.

Другой способ - скомпилировать ваш код в файлы .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 на clojure.org.

Ответ 2

Я очень новичок в Clojure, но я думаю, эта дискуссия в группах Clojure может быть решением и/или обходной путь, особенно сообщение Стюарта Сьерра 17 апреля в 22:40.

Ответ 3

В Common Lisp вы можете использовать условное чтение с функции.

#+testing (run-test 'is-answer-equal-42)

Выше будет читаться и, таким образом, выполняться во время загрузки, если список функций, привязанных к cl: * features *, будет содержать символ: тестирования.

Например

(let ((*features* (cons :testing *features*)))
   (load "/foo/bar/my-answerlib.lisp"))

временно добавит: тестирование списка функций.

Вы можете определить свои собственные функции и управлять теми выражениями, которые система Common Lisp читает и пропускает.

Дополнительно вы также можете:

#-testing (print '|we are in production mode|)

Ответ 4

Здесь также есть список различных возможностей http://rosettacode.org/wiki/Scripted_Main#Clojure. (Если вы найдете новый, пожалуйста, добавьте его. -))

Ответ 5

Возможно, вам стоит взглянуть на библиотеку test-is из clojure -contrib. Это не та же идиома, но она должна поддерживать довольно похожий рабочий процесс.

Ответ 6

Общие Lisp и Clojure (а также другие lisps) предоставляют интерактивную среду с REPL, и вам не нужны трюки типа "if __name__ == '__main__'". Для python существуют REPL-подобные среды: питон из командной строки, ipython, режим python для Emacs и т.д.

Вы должны просто создать библиотеку, добавить к ней testuite (существует много тестовых фреймворков для Common Lisp; я предпочитаю 5am, существует обзор доступных рамок здесь). А затем вы загружаете библиотеку, а в 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: execute-to-list. Я перехожу к коду этой функции, исправляю ошибку и перекомпилирую эту функцию. Затем я вызываю фиксированную функцию и гарантирую, что она работает. База данных в памяти не исчезает, она имеет такое же состояние, как и до перекомпиляции.

Ответ 7

Boot - это инструмент построения (альтернатива leiningen), который поддерживает скрипты. Таким образом, у вас может быть boot script, начинающийся с #!/usr/bin/env boot, который может иметь метод -main.

Вы также можете делать задачи, вызванные из командной строки, которые вызывают различные функции вашего кода. И у вас может быть задача упаковки, которая может создать uberjar для одной из этих функций в качестве точек входа.

Ответ 8

Если вы говорите о наличии "точки входа", вы можете это сделать:

(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, а затем основная функция будет вызываться пользователем.