Ich habe mal ein wenig mit Scala rumgespielt und mit den Sprachmöglichkeiten, die Scala bietet, versucht aus einem Java-lastigen Wicket-Beispiel etwas zu machen, was sich wie Scala anfühlt. Zuerst der Java-Code:
| Java | | copy code | | ? |
| 01 | package de.flapdoodle.incubator.scalawicket.web.pages; |
| 02 | |
| 03 | import org.apache.wicket.Component; |
| 04 | import org.apache.wicket.IClusterable; |
| 05 | import org.apache.wicket.ajax.AjaxRequestTarget; |
| 06 | import org.apache.wicket.ajax.markup.html.AjaxFallbackLink; |
| 07 | import org.apache.wicket.ajax.markup.html.AjaxLink; |
| 08 | import org.apache.wicket.markup.html.WebPage; |
| 09 | import org.apache.wicket.markup.html.basic.Label; |
| 10 | import org.apache.wicket.markup.html.form.Form; |
| 11 | import org.apache.wicket.markup.html.form.TextField; |
| 12 | import org.apache.wicket.model.CompoundPropertyModel; |
| 13 | import org.apache.wicket.model.IModel; |
| 14 | import org.apache.wicket.model.Model; |
| 15 | import org.apache.wicket.model.PropertyModel; |
| 16 | |
| 17 | public class StartJava extends WebPage |
| 18 | { |
| 19 | public StartJava() |
| 20 | { |
| 21 | IModel messageModel=Model.of("Huiii"); |
| 22 | final Label label=new Label("message",messageModel); |
| 23 | label.setOutputMarkupId(true); |
| 24 | |
| 25 | add(label); |
| 26 | |
| 27 | final Bean bean=new Bean(); |
| 28 | bean.setName("Klaus"); |
| 29 | bean.setAlter(12); |
| 30 | |
| 31 | final Component labelName = new Label("name",new PropertyModel(bean,"name")).setOutputMarkupId(true); |
| 32 | final Component labelAlter = new Label("alter",new PropertyModel(bean,"name")).setOutputMarkupId(true); |
| 33 | add(labelName); |
| 34 | add(labelAlter); |
| 35 | |
| 36 | final Form form=new Form("form", new CompoundPropertyModel(bean)); |
| 37 | form.add(new TextField("name")).add(new TextField("alter")); |
| 38 | form.setOutputMarkupId(true); |
| 39 | add(form); |
| 40 | |
| 41 | add(new AjaxFallbackLink("link",messageModel) |
| 42 | { |
| 43 | public void onClick(AjaxRequestTarget target) |
| 44 | { |
| 45 | getModel().setObject("Klick"); |
| 46 | bean.setName("Peter"); |
| 47 | target.addComponent(form); |
| 48 | target.addComponent(label); |
| 49 | target.addComponent(labelName); |
| 50 | target.addComponent(labelAlter); |
| 51 | } |
| 52 | }); |
| 53 | |
| 54 | add(new AjaxLink("link2",messageModel) |
| 55 | { |
| 56 | @Override |
| 57 | public void onClick(AjaxRequestTarget target) |
| 58 | { |
| 59 | getModel().setObject("Klack"); |
| 60 | bean.setAlter(24); |
| 61 | target.addComponent(form); |
| 62 | target.addComponent(label); |
| 63 | target.addComponent(labelName); |
| 64 | target.addComponent(labelAlter); |
| 65 | } |
| 66 | }); |
| 67 | } |
| 68 | |
| 69 | class Bean implements IClusterable |
| 70 | { |
| 71 | String _name; |
| 72 | int _alter; |
| 73 | public String getName() |
| 74 | { |
| 75 | return _name; |
| 76 | } |
| 77 | public void setName(String name) |
| 78 | { |
| 79 | _name = name; |
| 80 | } |
| 81 | public int getAlter() |
| 82 | { |
| 83 | return _alter; |
| 84 | } |
| 85 | public void setAlter(int alter) |
| 86 | { |
| 87 | _alter = alter; |
| 88 | } |
| 89 | } |
| 90 | } |
.. und nun das ganze in Scala
| Scala | | copy code | | ? |
| 01 | package de.flapdoodle.incubator.scalawicket.web.pages; |
| 02 | |
| 03 | import org.apache.wicket.Component; |
| 04 | import org.apache.wicket.IClusterable; |
| 05 | import org.apache.wicket.ajax.AjaxRequestTarget; |
| 06 | import org.apache.wicket.ajax.markup.html.AjaxFallbackLink; |
| 07 | import org.apache.wicket.ajax.markup.html.AjaxLink; |
| 08 | import org.apache.wicket.markup.html.WebPage; |
| 09 | import org.apache.wicket.markup.html.basic.Label; |
| 10 | import org.apache.wicket.markup.html.form.Form; |
| 11 | import org.apache.wicket.markup.html.form.TextField; |
| 12 | import org.apache.wicket.model.CompoundPropertyModel; |
| 13 | import org.apache.wicket.model.IModel; |
| 14 | import org.apache.wicket.model.Model; |
| 15 | import org.apache.wicket.model.PropertyModel; |
| 16 | import de.flapdoodle.incubator.scalawicket.web.wicket.WicketHelper._ |
| 17 | import scala.reflect.BeanProperty; |
| 18 | |
| 19 | class Start extends WebPage |
| 20 | { |
| 21 | val messageModel=Model.of("Huiii") |
| 22 | val label=new Label("message",messageModel) |
| 23 | label.enableAjax |
| 24 | |
| 25 | this+=label; |
| 26 | |
| 27 | val bean=new Bean |
| 28 | bean.name="Klaus"; |
| 29 | bean.alter=12; |
| 30 | |
| 31 | val labelName = new Label("name",bean model {_.name}).enableAjax |
| 32 | val labelAlter = new Label("alter",bean model {_.alter}).enableAjax |
| 33 | this+=labelName |
| 34 | this+=labelAlter |
| 35 | |
| 36 | val form=new Form[Bean]("form", new CompoundPropertyModel[Bean](bean)); |
| 37 | form+=(new TextField("name"),new TextField("alter")) |
| 38 | form.enableAjax |
| 39 | this+=form |
| 40 | |
| 41 | this+=new AjaxFallbackLink[String]("link",messageModel) |
| 42 | { |
| 43 | override def onClick(target: AjaxRequestTarget) = |
| 44 | { |
| 45 | this.model set "Klick" |
| 46 | bean.name="Peter" |
| 47 | target.refresh(form,label,labelName,labelAlter) |
| 48 | } |
| 49 | }; |
| 50 | |
| 51 | this+=new AjaxLink("link2",messageModel) |
| 52 | { |
| 53 | override def onClick(target: AjaxRequestTarget) = |
| 54 | { |
| 55 | this.model set "Klack" |
| 56 | bean.alter=24 |
| 57 | target.refresh(form,label,labelName,labelAlter) |
| 58 | } |
| 59 | }; |
| 60 | |
| 61 | class Bean extends IClusterable |
| 62 | { |
| 63 | @BeanProperty |
| 64 | var name: String = _; |
| 65 | @BeanProperty |
| 66 | var alter: Integer = _; |
| 67 | } |
| 68 | } |
Zuerst zu den offensichtlichen Dingen:
- Java: Zeilen=91, Wörter=155, Zeichen=2.567
- Scala: Zeilen=69(75%), Wörter=126(81%), Zeichen=1.921(75%)
In diesem Beispiel kann Scala seine Vorteile vielleicht noch nicht richtig ausspielen. Wobei 25% weniger Schreibarbeit (wenn man vernachlässigt, dass ich einmalig etwas Scala-Code schreiben musste, was mir das ermöglicht) für dieses Beispiel vielleicht auch schon eine bemerkenswerte Menge ist.
Spannender ist folgender Umstand: Ich habe den Java-Code aus dem Scala-Code abgeleitet. Wenn ich das Beispiel direkt in Java geschrieben hätte, sähe der Code fast genauso aus. Als ich aber den Scala-Code rückübersetzte, fiel es mir sehr schwer, die ganzen Typ-Definitionen zu schreiben, die abschließende Semikolons zu setzen und Parameter in geschweifte Klammern einzubetten. Auch wenn die Unterstützung für Scala in Eclipse noch einiges Potential hat, kann man a) damit bereits erstaunlich gut arbeiten (wenn man bedenkt, welche interessanten Sprachmöglichkeiten (und damit Schwierigkeiten für eine IDE) Scala bietet).
Meine schönste Zeile Code ist in diesem Beispiel das Erzeugen eines AbstractReadOnlyModel durch den Aufruf von “bean model { _.alter }”, der in diesem Fall das selbe leistet wie ein PropertyModel, nur dass das Attribut nicht erst per Reflection ermittelt wird.
Ich warte eigentlich darauf, dass jemand Wicket nach Scala portiert, denn das Lift-Framework gefällt mir nicht so gut (da wird wieder Code in die Templates verlagert). Jetzt geht es erst einmal weiter mit Java, auch wenn der Rückweg jetzt schon ein beschwerlicher war.


Bzgl: “das Lift-Framework [...] (da wird wieder Code in die Templates verlagert)”: soweit ich bisher über Lift lesen/verstehen konnte, soll und wird gerade dies vermieden.
Ich gebe zu daß ich bisher Scala nur leicht angeschnuppert habe und keine Zeile mit Lift geschrieben habe, aber … im Dokument “Starting with Lift” wird in Kapitel 1.1 explizit auf Wicket als Vorbild verwiesen. Und am Ende des Kapitels 2.6:
http://liftweb.net/docs/getting_started/mod_master.html#x1-120002.6
“In Lift, display can creep into a snippet, but business logic cannot creep into a the static display template. Yes, your designers will still have to police putting display logic in the snippet code, but the coders will not have to police business logic in the templates. “
Die Frage, was “Code im Template” bedeutet, ist vermutlich Ansichtssache. Aber wenn man es ganz streng nimmt, dann bedeutet das z.B. keine Schleifen, keine Expressions, keine bedingten Formatierungen, z.B. lift:Util.out verstößt IMHO gegen dieses Prinzip. Nun muss man das nicht so streng sehen wie ich, aber ich finde die Wicket-Variante noch aus einem zweiten Grund besser: bis auf wenige Ausnahmen steht für eine Komponente immer wicket:id=”meineKomponente”, mehr nicht. Bei Lift habe ich das Gefühl, dass ich über das reine Komponentenmapping noch mehr machen kann, aber auch mehr lernen muss.
Daher war ich etwas verwundert, als ich damals bei Lift gelesen habe, dass man sich auch Wicket als Vorbild genommen hat.