Designing an SQL-based application

1 view
Skip to first unread message

Janico Greifenberg

unread,
May 11, 2009, 4:05:57 AM5/11/09
to clo...@googlegroups.com
Hi,

I'm writing an RSS-reader using compojure and clojure.contrib.sql. As
this is my first project in a functional language, I'm not sure about
how to design the application. The concrete question I'm having right
now, is how to encapsulate database queries in functions. For my
RSS-reader, a basic operations is to display lists of feeds that I
retrieve with the following code:

(sql/with-connection db
(sql/with-query-results
feeds
["select id, title from feeds order by title"]

As this happens at a few places in the code, I would like to
encapsulate it somehow. I see several approaches to this, but I'm
uncertain which of them is the most idiomatic way to do this in
Clojure.

(1) The straightforward approach seems to be a function that returns
the feeds. However, I would have to call (doall feeds) to get the data
out of the internals of contrib.sql/JDBC and into my application. So I
guess this would not be the most efficient way.

(2) Another idea is to create a function or macro with-feeds that
wraps the query and creates a binding to be used inside the body.

(3) Or I could have a function that takes another function as a
parameter which gets called inside with-query-results and gets feeds
as parameter.

Which of these variants -- or another one that I did not think of --
would you recommend?

Janico

Sean Devlin

unread,
May 11, 2009, 11:32:43 AM5/11/09
to Clojure
Here are my thoughts on the three approaches:

Approach #1: This seems the most straightforward. I'd write a
function that takes a map of conditions, and returns a list of
tuples. You can then do what you want with the list of tuples.

Approach #2: Remember the first rule of macro club: Don't write
macros if a function will do. From what you've described, this
application wraps a simple SELECT * FROM... , and a macro is overkill.

Approach #3: This is a good approach once you hit the limits of the
first approach, and before you move on to the second approach.

I've written SQL apps in Clojure too. And I've used approaches 1 and
3. I only use approach 2 when I really have to, and I try to have the
macro do as little as possible.

In summary, try approaches:

1 -> 3 -> 2

I'll try to throw some code on github this evening so you can see an
example of what I do, and you can decide if it's a good fit for your
application.

Sean

Victor Rodriguez

unread,
May 11, 2009, 12:04:44 PM5/11/09
to clo...@googlegroups.com
On Mon, May 11, 2009 at 11:32 AM, Sean Devlin <francoi...@gmail.com> wrote:
>
> Here are my thoughts on the three approaches:
>
> Approach #1:  This seems the most straightforward.  I'd write a
> function that takes a map of conditions, and returns a list of
> tuples.  You can then do what you want with the list of tuples.
>
> Approach #2:  Remember the first rule of macro club:  Don't write
> macros if a function will do.  From what you've described, this
> application wraps a simple SELECT * FROM... , and a macro is overkill.

Yes, you should not write a macro when a function will do, but I think
you might be misinterpreting the "spirit" of the rule.

Also, the "bottom-up" aspect of programming in Lisp should be kept in
mind. In other words, you want to "write a language for solving your
problem", and you should definitively use macros for that.

So, if a "with-feeds" macro makes sense for the application, do write
it! Just make sure the macro is simply syntax sugar for your
functions. Following a pattern common in Scheme (at least), the
implementation would run along the lines of:

(defn call-with-feeds
[f]
"Call a function with the feeds as an argument"
...)

(defmacro with-feeds
[feeds & body]
`(call-with-feeds (fn [~feeds] ~@body)))

Kind regards,

Victor Rodriguez.

Sean Devlin

unread,
May 11, 2009, 12:39:51 PM5/11/09
to Clojure
Okay, good point about approach #2. As I mentioned earlier, I'd use
approach #3 first. Here's how I'd write your macro as a function

(defn process-feeds
[feeds body]
(body feeds))

And I'd call it like this

(process-feeds (get-feeds-s-exp ...) (fn [feeds] body))

The first thing I'd like to mention is that this is VERY similar to
your with-feeds macro. I think your general approach is sound.
Following is simply my thoughts and opinions on avoiding a macro in
this case.

I like this approach better because it segregates the calling from the
processing, and it lets you test each routine independently. Also,
forcing the second argument to be a function encourages defining the
function, which in turn encourages testing.

Now, I've made an assumption, and that is the process-feeds has no
side effects. If you are doing something that is sequential, not
functional (like opening/closing a resource), then a macro makes
sense.

My thoughts in a pithy sound-byte:

If you are abstracting away a function, try to use a function.
If you are abstracting away a sequence, it's the right time to use a
macro.

There are other classes of problems in the function-vs-macro debate,
and I we can save those for another day :)

On May 11, 12:04 pm, Victor Rodriguez <vict...@gmail.com> wrote:

Janico Greifenberg

unread,
May 12, 2009, 5:15:11 AM5/12/09
to clo...@googlegroups.com
Thanks for your responses. I'm glad to see that I'm not entirely on
the wrong track here.

On Mon, May 11, 2009 at 6:39 PM, Sean Devlin <francoi...@gmail.com> wrote:
>
> Okay, good point about approach #2.  As I mentioned earlier, I'd use
> approach #3 first.  Here's how I'd write your macro as a function
>
> (defn process-feeds
>  [feeds body]
>  (body feeds))
>
> And I'd call it like this
>
> (process-feeds (get-feeds-s-exp ...) (fn [feeds] body))
>
> The first thing I'd like to mention is that this is VERY similar to
> your with-feeds macro.  I think your general approach is sound.
> Following is simply my thoughts and opinions on avoiding a macro in
> this case.
>
> I like this approach better because it segregates the calling from the
> processing, and it lets you test each routine independently.  Also,
> forcing the second argument to be a function encourages defining the
> function, which in turn encourages testing.

I decided to go with the macro, because it makes the default scenario
very convenient. The tests I write apply to the functions where the
feeds operation is performed (and I have tests for the macro). When
the need arises to have an explicitly defined and tested function, I
can still use call-with-feeds without the macro.
Reply all
Reply to author
Forward
0 new messages