2009-06-10 11 views
24

Mi sto dilettando in clojure e sto avendo un piccolo problema nel cercare di determinare l'equivalente del clojure (e/o Lisp) di questo idioma di python comune.Qual è l'equivalente del clojure dell'idioma Python "if __name__ == '__main__'"?

L'idioma è che in fondo a un modulo Python v'è spesso un po 'di codice di test, e poi una dichiarazione che esegue il codice, ad esempio:

# 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() 

Questo è utile per semplice, annuncio -hoc test. Normalmente si userebbe questo modulo scrivendo from mymodule import MyClass, nel qual caso il numero _runTests() non viene mai chiamato, ma con lo snippet alla fine, si può anche eseguirlo digitando python mymodule.py direttamente dalla riga di comando.

Esiste un linguaggio equivalente in Clojure (e/o Common Lisp)? Non sto cercando una vera e propria libreria di test di unità (beh, lo sono, ma non in questa domanda), vorrei solo includere del codice in un modulo che verrà eseguito solo in alcune circostanze, quindi posso avere un modo rapido per eseguire il codice su cui stavo lavorando, ma permetto comunque che il mio file venga importato come un normale modulo/spazio dei nomi.

risposta

27

Non è idiota eseguire gli script Clojure più e più volte dalla riga di comando. Il REPL è una linea di comando migliore. Clojure essendo un Lisp, è comune lanciare Clojure e lasciare la stessa istanza in esecuzione per sempre, e interagire con essa piuttosto che riavviarla. È possibile modificare le funzioni nell'istanza in esecuzione una alla volta, eseguirle e inserirle secondo necessità. Sfuggire al noioso e lento ciclo di modifica/compilazione/debug tradizionale è una grande funzionalità di Lisps.

È possibile scrivere facilmente le funzioni per eseguire operazioni come i test delle unità di corsa e richiamare tali funzioni dal REPL ogni volta che si desidera eseguirle e ignorarle altrimenti. È comune in Clojure utilizzare clojure.contrib.test-is, aggiungere le funzioni di test al proprio spazio dei nomi, quindi utilizzare clojure.contrib.test-is/run-tests per eseguirle tutte.

Un altro buon motivo per non eseguire Clojure dalla riga di comando è che il tempo di avvio della JVM può essere proibitivo.

Se si desidera eseguire uno script Clojure dalla riga di comando, ci sono molti modi in cui è possibile farlo. Vedi the Clojure mailing list per qualche discussione.

Un modo è verificare la presenza di argomenti della riga di comando. Dato questo foo.clj nella directory corrente:

(ns foo) 

(defn hello [x] (println "Hello," x)) 

(if *command-line-args* 
    (hello "command line") 
    (hello "REPL")) 

Otterrete un comportamento diverso a seconda di come si inizia 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=> 

Vedi src/clj/clojure/main.clj nella fonte Clojure se si vuole vedere come questo funziona.

Un altro modo è compilare il codice nei file .class e richiamarli dalla riga di comando di Java. Dato un file sorgente foo.clj:

(ns foo 
    (:gen-class)) 

(defn hello [x] (println "Hello," x)) 

(defn -main [] (hello "command line")) 

Creare una directory per memorizzare i file compilati .class; questo valore predefinito è ./classes. Devi creare questa cartella da solo, Clojure non la creerà. Assicurati inoltre di impostare $CLASSPATH per includere ./classes e la directory con il tuo codice sorgente; Immagino che foo.clj sia nella directory corrente. Così dalla riga di comando:

$ mkdir classes 
$ java -cp ~/path/to/clojure.jar:./classes:. clojure.main 
Clojure 1.1.0-alpha-SNAPSHOT 
user=> (compile 'foo) 
foo 

Nella directory classes si avranno ora un gruppo di .class file.Per richiamare il codice dalla riga di comando (in esecuzione della funzione -main per impostazione predefinita):

$ java -cp ~/path/to/clojure.jar:./classes foo 
Hello, command line. 

C'è un sacco di informazioni sulla compilazione del codice Clojure su clojure.org.

1

Sono molto nuovo a Clojure ma penso che this discussion sui gruppi Clojure possa essere una soluzione e/o soluzione, in particolare il post di Stuart Sierra il 17 aprile alle 22:40.

0

Si potrebbe volere dare un'occhiata alla libreria test-is da clojure-contrib. Non è lo stesso idioma, ma dovrebbe supportare un flusso di lavoro molto simile.

1

In Common Lisp è possibile utilizzare la lettura condizionale con features.

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

di cui sopra saranno di sola lettura e quindi eseguire durante il caricamento, se l'elenco delle caratteristiche legate al cl:*features* conterrà il simbolo: il test.

Per esempio

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

sarà temporaneamente aggiungere: test per l'elenco delle caratteristiche.

È possibile definire le proprie caratteristiche e controllare quali espressioni il sistema Common Lisp legge e quali salta.

Inoltre si può anche fare:

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

Non penso che * le caratteristiche * siano adatte a questo. * caratteristiche * mostra le funzionalità disponibili, non alcuni stati dell'ambiente o richieste di esecuzione del codice. –

+0

perché no? * caratteristiche * utilizzate per tutti i tipi di cose: per descrivere l'hardware su cui è in esecuzione la cosa, alcune librerie core disponibili, alcune modalità del software, la versione dell'implementazione Lisp, la versione della lingua, se è: produzione- mode o: development-mode, ecc. –

0

Common Lisp e Clojure (così come altre lische) forniscono ambiente interattivo con REPL, e non avete bisogno di trucchi come «if __name__ == '__main__'». Ci sono ambienti REPL-come per Python: il pitone da riga di comando, ipython, modalità di pitone per Emacs, ecc

Si dovrebbe solo creare la libreria, aggiungere una suite di test ad essa (ci sono molti framework di test per Common Lisp ; Preferisco il framework 5am, c'è un sondaggio di quadri disponibili here). E quindi carichi la libreria, esegui test, funzioni di chiamata, esperimento, ecc.

Quando trovi un test non funzionante, fai una correzione, ricompila il cambiamento codice, e continua a sperimentare, eseguendo test senza riavviare l'intera applicazione.Ciò consente di risparmiare molto tempo, poiché l'applicazione in esecuzione potrebbe aver accumulato molto stato (potrebbe aver creato finestre gui, connesso a database, raggiunto un momento critico che non è facilmente riproducibile) e non è necessario riavviarlo dopo ogni cambiamento.

Ecco un esempio per Common Lisp (dalla mia biblioteca cl-sqlite):

Il codice:

(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)")))) 
... 

e la sessione interattiva:

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> 

Supponiamo ora che ho trovato bug in sqlite: execute-to-list. Vado al codice di questa funzione, correggi il bug e ricompilino questa funzione. Quindi chiamo la funzione fissa e garantisco che funzioni. Il database in memoria non è andato, ha lo stesso stato che aveva prima di ricompilare.

+3

L'__name __ == '__ main__' idiom non ha nulla a che fare con il REPL - è un metodo per distinguere tra "importato come un modulo" e "eseguito come uno script". Il codice in genere non è il codice noodling e di prova che avresti sperimentato al REPL, ma codice che vorresti eseguire esattamente allo stesso modo ripetutamente. Il codice di prova è un esempio, ma il più comune è di solito avere uno script che consenta anche il riutilizzo come modulo. – Brian

+0

Sì, in generale, quelle sono cose diverse. Ma nel contesto di questa domanda, per __name__ è stato usato il controllo per eseguire (ed eseguire nuovamente) i test, e REPL è idiomatico per tale caso d'uso in lisps. –

+0

L'utente ha richiesto il nome == idi principale, NON un repl, NON una suite di test. – mcandre

-3

Se si sta parlando di avere un "punto di ingresso" si può certamente farlo:

(ns foo) 

(defn foo [n] 
    (inc n)) 

(defn main [] 
    (println "working") 
    (println "Foo has ran:" (foo 1))) 

(main) 

cosa accadrà ora è che ogni volta che questo codice è (load-file "foo.clj")' d o (usa 'foo) o (richiede' foo), quindi (main) verrà chiamato, di solito non è fatto.

Molto più comune è che un file di codice può essere caricato al REPL e quindi la funzione principale verrà chiamata dall'utente.

+0

Questo può essere fatto in modo che (main) venga attivato solo quando foo.clj viene eseguito direttamente e non quando viene caricato da un altro script? – mcandre

+0

Io non la penso così perché in entrambi i casi dovresti valutare (e poi compilare) tutte le espressioni. C'è sempre una compilazione AOT che consente la definizione di un punto di ingresso: http://clojure.org/compilation – Chris

0

Boot è un tool di costruzione (un'alternativa a leiningen), quello supports scripts. Quindi potresti avere uno script di avvio che inizia con #!/usr/bin/env boot che può avere un metodo -main.

È anche possibile eseguire attività richiamate dalla riga di comando che richiama diverse funzioni del codice. E potresti avere un'attività di packaging che può creare un uberjar per una di queste funzioni come punti di ingresso.