5

Clojure offers a good Java interop. However, I really want to have this:

(servlet IndexServlet
  (service[parmas] ....)
  (do-post[params] ....)
  (do-get [params] ....))

(servlet-filter SecurityFilter
  (do-filter [params] ....))

I guess that is what called a DSL and in Lisp world it is done via Macros.

I'm not sure how/where to start. refiy and extends forms are definitely have important role here but I don't know how that would fit into Macros.

How start doing this DSL?
A snippet, tips and tricks are really appreciated.

Chiron
  • 20,081
  • 17
  • 81
  • 133

1 Answers1

3

You may want to look into Ring's Jetty adapter for an example of a servlet implementation in Clojure. The source is available here (link to source for the 1.1 release). In particular, the first function defined in that namespace, proxy-handler returns a handler based on an abstract class provided by Jetty.

If you choose to implement a similar approach (basing your servlet on a Java class providing some ready-made method impls), you'll need to use proxy; if you only need to implement interfaces (with no subclassing), then you'll probably want reify instead. Whether or not macros will be useful depends on which parts of the implementation will turn out to be fixed; Ring's Jetty adapter wouldn't benefit from the use of macros, but you may (for example if you wish to make the class to be extended / interface to be implemented into an argument, as the question seems to indicate).

In any case, whichever functionality you choose to implement will need to be part of an interface or protocol. So, implementing javax.servlet.Servlet plus an additional operation foo might look like this:

(import (javax.servlet Servlet ServletRequest ServletResponse))

(defprotocol PFoo
  (foo [this x y z]))

(reify
  Servlet
  (service [this ^ServletRequest req ^ServletResponse res]
    ...)
  ;; other Servlet methods here...
  PFoo
  (foo [this x y z]
    ...))

You could then wrap this in a macro to provide any desired syntactic sugar. Note that reify does not actually care about the way in which you interleave interface / protocol names and method definitions inside its body, so you could have your macro emit

(reify
  Servlet PFoo ... ; other interfaces & protocols
  (service [...] ...)
  (foo [...] ...)
  ;; other methods
  )

if that's more convenient.

A sketch of a macro taking a name of a servlet interface to implement (presumably extending javax.servlet.Servlet) and injecting a protocol with some extra methods:

(defprotocol PFancyServlet
  (do-get [this ...])
  (do-post [this ...]))

(defmacro servlet [servlet-iface & meths]
   `(reify ~servlet-iface PFancyServlet
      ~@meths))

meths will need to include do-get and do-post as well as servlet-iface methods; you could add some argument validation to make sure this is the case. An example call:

(servlet SomeServletInterface
  (service [this ...] ...)
  ;; ...
  (do-get [this ...] ...)
  (do-post [this ...] ...))
Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • Awesome! How can I have functions without 'this' as the first parameter? (servlet IndexServlet (do-get [request response])) – Chiron Jun 02 '13 at 18:12
  • You may need to make use of `this` in method bodies (if the methods call each other, say). That being said, your macro can preprocess `meths` in arbitrary ways, in particular adding `this` back in if you omit it in the user-level syntax: `(let [[meth-name params & body] meth] \`(~meth-name ~(vec (cons 'this params)) ~@body))` or something similar. That would produce an expansion in which `this` would be available as a "magic parameter" in each of the methods' bodies. Or you could use `(gensym "this__")` in place of `'this` in the expansion to "hide" `this` if that's what you want. – Michał Marczyk Jun 02 '13 at 18:30
  • Also, `proxy` already makes `this` implicit (see `(doc proxy)`), as do `definterface`, `gen-interface` and `gen-class`; `reify`, `deftype`, `defrecord`, `defprotocol` do not. (Re: `gen-class`, it is the method declarations in a `gen-class` form which omit `this`; the implementing functions must make it explicit.) – Michał Marczyk Jun 02 '13 at 18:43
  • My goal is to implement a web framework. What I want to have is Macro named "servlet" where the end-developer can only implement the methods he need (like do-get, do-post, do-head ...) So my role (as the framework author) is give developers a generic and high level API. Does this change the above answer? – Chiron Jun 02 '13 at 20:01
  • Which part? If you mean whether I still think there might be a need to use `this` in method bodies, I guess it's inevitable that there'll be (edge?) cases where the developer wants to do something special, so it's useful to expose it somehow, but of course there are ways of doing this not involving making it an explicit parameter (injecting a "magic `this`" is one, you could also make it available by explicit request). Good luck with your framework! – Michał Marczyk Jun 02 '13 at 22:38
  • I would like to allow the end-developers to use these names: do-get, do-post, do-delete instead of the "Java" Servlets methods names: doGet, doPost .. I'm not sure how to achieve this – Chiron Jun 03 '13 at 06:55
  • As I said, you can preprocess the inputs to your macro in arbitrary ways. In particular, you can take each method implementation and replace the leading symbol (naming the method) with the result of applying a hyphenated-name-to-camel-case transformation. Or you could introduce a protocol with such methods and then have your framework implement the Java methods in terms of the outputs of the developer-implemented protocol methods. There's quite a large design space here. – Michał Marczyk Jun 03 '13 at 07:32