28 grudnia 2010

Google Guice z Clojure - niebagatelna rola IRC w moim rozwoju

Do moich poczynań z JRuby on Rails i komentarzy do wpisu Świąteczne próby z JRuby on Rails wrócę w kolejnym wpisie. Dzisiaj będzie o czymś troszkę innym.

Zdumiewające, jak wiele wiedzy można zdobyć stosując wciąż konwencjonalne sposoby nauki. IRC to narzędzie stare jak sam Internet i pamiętam, ile sprawiało mi przyjemności i satysfakcji, kiedy spędzałem godziny siedząc przed terminalem na uczelni klepiąc dyskusje na jakimś bliżej nieokreślonym kanale. Nie miało znaczenia, gdzie, z kim i o czym, byle można było odsiedzieć swoje przez terminalem. Człowiek nic nie robi i czas mu leci - wtedy to był boski stan.

Przez długi czas nie uważałem IRCa za poważne narzędzie. Kojarzyło mi się właśnie z tym błogim stanem nic-nie-robienia, bo w zasadzie w Sieci jeszcze niewiele można było wtedy robić. Kiedy przyłączyłem się do Apache OpenEJB moje postrzeganie IRCa nieznacznie przesunęło się w stronę większego użycia przy rozwiązywaniu problemów - łatwiej było dopytać o szczegóły działania tego czy innego ustrojstwa. Raczej z rzadka tam zaglądałem, bojąc się nieprzerwanych dyskusji, które fajnie było prowadzić, ale w podsumowaniu wyglądały mizernie.

Nie wiem dlaczego, ale postanowiłem uruchomić Google Guice z poziomu Clojure. Czasami takie pomysły są zarzewiem kolejnych, więc nie zastanawiając się długo, zabrałem się za temat.

Początek był trywialny. Chwila zastanowienia i...potrzebny jest Guice w CLASSPATH do Clojure REPL. Nie inaczej, kiedy klasa javowa chce wywołać inną - obie muszą (zwykle) siebie widzieć przez CLASSPATH. Uruchamiam Clojure 1.3.0-alpha4 z odpowiednimi bibliotekami Guice.
jacek:~
$ CLASSPATH=~/apps/guice/guice-3.0-rc1.jar:~/apps/guice/javax.inject.jar:~/apps/guice/aopalliance.jar clj -13
CLOJURE_DIR:  /Users/jacek/apps/clojure
CLOJURE_CONTRIB_JAR:  /Users/jacek/apps/clojure-contrib-1.3.0-alpha3.jar
Clojure 1.3.0-alpha4
user=>
Pierwsza biblioteka oczywista, a dwie pozostałe wyszły "w praniu", kiedy kolejno uruchamiałem potrzebne elementy Guice.

Skracając znacznie moją historię, dotarłem do momentu, w którym miałem następujący stan:
user=> (import '(com.google.inject Guice AbstractModule))
com.google.inject.AbstractModule
user=> (def module (proxy [AbstractModule] [] (configure [] (println "configure called"))))
#'user/module
user=> (Guice/createInjector module)
IllegalArgumentException No matching method found: createInjector  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:77)
I tu mała zagwozdka. W javadoc dla com.google.inject.Guice jest metoda createInjector, która powinna akceptować mój moduł. Zresztą niejedna.

I tak stan dumania, co by tu począć, trwał cały dzień. W zasadzie trochę dłużej, bo już wczoraj odnotowałem sobie, gdzie stanąłem i zabrałem się za dalsze dumanie z samego rana.

Wieczorem miałem dosyć. Postanowiłem napisać na grupę użytkowników clojure i iść spać. I już miałem pisać, kiedy przypomniałem sobie o kanale #clojure. I to był strzał w dziesiątkę!
[11:22pm] jlaskowski: hi
[11:23pm] jlaskowski: I'm having troubles with java.lang.IllegalArgumentException: No matching method found: createInjector
[11:23pm] jlaskowski: CLASSPATH=`pwd`/guice/guice-3.0-rc1.jar:`pwd`/guice/javax.inject.jar:`pwd`/guice/aopalliance.jar clj -13
[11:23pm] jlaskowski: CLOJURE_DIR:  /Users/jacek/apps/clojure
[11:23pm] jlaskowski: CLOJURE_CONTRIB_JAR:  /Users/jacek/apps/clojure-contrib-1.3.0-alpha3.jar
[11:23pm] jlaskowski: Clojure 1.3.0-alpha4
[11:23pm] jlaskowski: (def mm (proxy [AbstractModule] [] (configure [] (println "configure called"))))
[11:23pm] jlaskowski: (bean mm)
[11:24pm] jlaskowski: {:class user.proxy$com.google.inject.AbstractModule$0}
[11:24pm] jlaskowski: and when I call (Guice/createInjector mm)
[11:24pm] jlaskowski: it spits out the exception
[11:24pm] jlaskowski: http://google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/AbstractModule.html
[11:25pm] jlaskowski: how can I find out what is wrong exactly?
[11:25pm] jlaskowski: how do I find out what Clojure can call upon a class?
[11:26pm] jlaskowski: any helpful reflection methods to use?
[11:26pm] amalloy: jlaskowski: createInjector takes an array of Modules, not a single module
[11:27pm] amalloy: java's Foo.bar(Whatever...) is sugar for arrays
[11:27pm] jlaskowski: right
[11:27pm] jlaskowski: but have a look at the full stack trace
[11:27pm] jlaskowski: user=> (.printStackTrace *e)
[11:27pm] jlaskowski: java.lang.IllegalArgumentException: No matching method found: createInjector
[11:27pm] jlaskowski: at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:77)
[11:27pm] jlaskowski: at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:202)
[11:27pm] jlaskowski: at user$eval17.invoke(NO_SOURCE_FILE:4)
[11:27pm] jlaskowski: at clojure.lang.Compiler.eval(Compiler.java:6201)
[11:27pm] jlaskowski: at clojure.lang.Compiler.eval(Compiler.java:6168)
[11:27pm] jlaskowski: at clojure.core$eval.invoke(core.clj:2680)
[11:27pm] jlaskowski: at clojure.main$repl$read_eval_print__5619.invoke(main.clj:179)
[11:27pm] jlaskowski: at clojure.main$repl$fn__5624.invoke(main.clj:200)
[11:27pm] jlaskowski: at clojure.main$repl.doInvoke(main.clj:200)
[11:27pm] jlaskowski: at clojure.lang.RestFn.invoke(RestFn.java:422)
[11:27pm] jlaskowski: at clojure.main$repl_opt.invoke(main.clj:266)
[11:27pm] jlaskowski: at clojure.main$main.doInvoke(main.clj:361)
[11:27pm] jlaskowski: at clojure.lang.RestFn.invoke(RestFn.java:437)
[11:27pm] jlaskowski: at clojure.lang.Var.invoke(Var.java:409)
[11:27pm] jlaskowski: at clojure.lang.AFn.applyToHelper(AFn.java:169)
[11:27pm] jlaskowski: at clojure.lang.Var.applyTo(Var.java:518)
[11:27pm] jlaskowski: at clojure.main.main(main.java:37)
[11:27pm] amalloy: augh do not do that
[11:27pm] amalloy: http://gist.github.com
[11:28pm] jlaskowski: amalloy: ok, will use it
[11:29pm] amalloy: anyway, yes. it can't find a method with that name whose parameters are (Foo), only one whose parameters are (Foo[])
[11:29pm] jlaskowski: you're right!
[11:29pm] jlaskowski: so how can I call a method that accepts an array?
[11:29pm] amalloy: you can find this out for yourself with clojure.contrib.repl-utils/show
[11:30pm] amalloy: jlaskowski: ##(doc into-array) is one way; in this case it looks like Guice will also accept an Iterable, so you can just pass it a clojure vector
[11:30pm] sexpbot: ⟹ "([aseq] [type aseq]); Returns an array with components set to the values in aseq. The array's component type is type if provided, or the type of the first value in aseq if present, or Object. All values in aseq must be compatible with the component type. Class objec... http://gist.github.com/756642
[11:31pm] jlaskowski: HURRAYY - you saved my day, amalloy
[11:31pm] jlaskowski: it works fine now
[11:32pm] Luyt_: Freenode's channels are a great resource, because of all the friendly and knowledgeable people on it!
[11:32pm] jlaskowski: it's awesome how quickly it's sorted out!
[11:32pm] Luyt_: ...and some communities are really great.
[11:32pm] lancepantz: we just have a great community 
[11:32pm] jlaskowski: it's not the first time I just enter the channel
[11:33pm] jlaskowski: and get an answer in secs
[11:33pm] jlaskowski: unbelievable
[11:33pm] jlaskowski: thanks again
[11:34pm] Luyt_: as long as you don't become a Help Vampire (http://slash7.com/2006/12/22/vampires/) it's allright 
[11:35pm] Luyt_: and you can always give back to the community, of course. Like lurking and answering questions from noobs, which are too tedious for the advanced clojurists in here. (Same works in #python)
I to było dokładnie to, czego potrzebowałem! Niecały kwadrans i odpowiedź już jest. W zasadzie odpowiedź była minutę po tym, kiedy skończyłem pisać - użyć tablicy zamiast po prostu przekazywać moduł. Ech, człowiek ślepnie na starość.

Wystarczy użyć operatora tablicowego i Guice będzie kontent.
user=> (Guice/createInjector [module])
configure called
#<InjectorImpl Injector[bindings=[ProviderInstanceBinding[key=Key[type=com.google.inject.Injector, annotation=[none]], source=[unknown source], scope=Scopes.NO_SCOPE, provider=Provider<Injector>], ProviderInstanceBinding[key=Key[type=java.util.logging.Logger, annotation=[none]], source=[unknown source], scope=Scopes.NO_SCOPE, provider=Provider<Logger>], InstanceBinding[key=Key[type=com.google.inject.Stage, annotation=[none]], source=[unknown source], instance=DEVELOPMENT]]]>
Tylko, po co mi to było?! :)