[newb] clojure / ruby yield comparison

78 views
Skip to first unread message

Daniel Bush

unread,
Oct 25, 2009, 9:55:06 AM10/25/09
to Clojure
hi,
I'm new to lisp/clojure
in general. I was reading the free whitepaper from Amit
Rathore's 'Clojure in Action' where he gives a simple
example on macros.

In the example on page 9 he shows how you might do an
audited connection in java:

public void addExpense(long userId, Date date, BigDecimal amount) {
AuditLogger.logLookup(userId, AuditCode.ADD_EXPENSES);
ExpensesConnection connection = new ExpensesConnection(userId);
connection.saveNewExpense(date, amount);
connection.flush();
connection.close();
}

In clojure he shows a macro (without showing the code[1])
that boils it down to:

(defn add-expense [user-id date amount]
(with-audited-connection [user-id connection]
(save-new-expense connection date amount)))

So in ruby you can be pretty concise here - in fact
I differ with the author who says it isn't as good:

def add_expense user_id , date , amount
with_audited_connection(user_id,'audit code') do |conn|
conn.save_new_expense date , amount
end
end

where with_audited_connection might be something like:

def with_audited_connection user_id , audit_code
puts "logging conn for '#{user_id}' (#{audit_code})"
conn = ... # Get a connection.
yield conn # Expose conn in block - see above.
puts "closing conn or whatever"
end

('puts' just means print to stdout)

The second function above organises the connection 'conn'
and then yields it for use elsewhere in a block (see the
do-block in the first function). After that block finishes,
the yield returns and the the 2nd function tidies up and
does whatever else it needs to.


Bear in mind, I'm really new to lisp in general.
So is 'connection' in
(with-audited-connection [user-id connection] ... )
being filled in by the macro or is the example assuming that
'connection' has already been defined elsewhere?
Can clojure do something like the 'yield' in the ruby
example above (which I think is one of ruby's strengths
coming from smalltalk)?

I know you might say something like: "go learn some lisp
and macros and you'll know what the author is trying to
say", but I was hoping for just a quick indication on what is
going on here - I'll continue learning clojure in the
background anyway so I guess it'll eventually twig.

Cheers,
Daniel Bush

[1] I know the example is just there to whet the appetite
and is not part of a particular lesson or chapter.

James Reeves

unread,
Oct 25, 2009, 10:31:17 AM10/25/09
to Clojure
I don't think that's a very good example of what macros can do. As you
point out, you can do much the same thing in Ruby with blocks:

with_open(MyStream.new) do |stream|
stream.write "Hello"
end

In Clojure, blocks are analogous to anonymous functions. So the
equivalent Clojure would be:

(with-open (MyStream.)
(fn [stream]
(.write stream "Hello")))

You could use a macro to make this more concise:

(with-open [stream (MyStream.)]
(.write stream "Hello"))

But whilst this is useful, this doesn't really demonstrate why macros
are so powerful. Macros are useful because they automatically
rearrange your source code into something else. They're most similar
to the Ruby 'eval' function, but operate of data structures rather
than strings.

For example, the macro '->' changes this:

(-> x (foo y) (bar z))

Into this:

(foo (bar x z) y)

- James

Jarkko Oranen

unread,
Oct 25, 2009, 4:39:35 PM10/25/09
to Clojure
> But whilst this is useful, this doesn't really demonstrate why macros
> are so powerful. Macros are useful because they automatically
> rearrange your source code into something else. They're most similar
> to the Ruby 'eval' function, but operate of data structures rather
> than strings.
>
Nitpick, but... The function closest to ruby's eval would be 'eval'.
Except clojure's eval of course operates on data structures. Macros
are a feature of the Lisp evaluation model that allows you to
intercept the evaluator and run your own code.

Macros have two differences to normal functions:
1) Arguments passed to them are *not* evaluated, and
2) They are run after read-time, but before compile-time, and their
output is evaluated in place of the invocation.

Point 1 means that you don't need a lot of quoting when passing "code
literals" to macros, and point 2 is what allows these functions to be
used for syntactic abstraction, as a macro form can inspect and
transform its input parameters in any way imaginable, as long as the
output is also valid Clojure code.

The full power of macros takes a while to sink in. Because the entire
language is available, one can for example use macros with Java
reflection to programmatically produce Clojure wrappers for arbitrary
Java methods, at compile time.

Often you can solve a problem using higher order functions instead of
macros, like Daniel did with Ruby, but at times you will be thinking
"I wish I could just write..." and that's when you'll wish you had
macros. :)

--
Jarkko

Radford Smith

unread,
Oct 25, 2009, 2:50:33 PM10/25/09
to Clojure
Ruby blocks are anonymous functions with syntax sugar. You could write
James' with_open method like this:

def with_open(stream, &f)
f.call(stream)
end

The equivalent in Clojure is effectively the same:

(defn with-open [stream f]
(f stream))

John Harrop

unread,
Oct 25, 2009, 6:26:52 PM10/25/09
to clo...@googlegroups.com
On Sun, Oct 25, 2009 at 2:50 PM, Radford Smith <rads...@gmail.com> wrote:

Ruby blocks are anonymous functions with syntax sugar. You could write
James' with_open method like this:

def with_open(stream, &f)
 f.call(stream)
end

The equivalent in Clojure is effectively the same:

(defn with-open [stream f]
 (f stream))

What about exception safety? Shouldn't that really be

(defn with-open [stream f]
  (try
    (f stream)
    (finally
      (.close stream))))

Daniel Bush

unread,
Oct 25, 2009, 9:43:16 PM10/25/09
to Clojure


On Oct 26, 9:26 am, John Harrop <jharrop...@gmail.com> wrote:
> On Sun, Oct 25, 2009 at 2:50 PM, Radford Smith <radscr...@gmail.com> wrote:
>
> > Ruby blocks are anonymous functions with syntax sugar. You could write
> > James' with_open method like this:
>
> > def with_open(stream, &f)
> >  f.call(stream)
> > end
>

Yeah, sorry I don't think about these things too heavily sometimes.
Ruby's yield is a sexy way of doing this.

Also, you can create anonymous functions out in the open like this:

f = lambda do |arg1,arg2|
....
end
f.call arg1 , arg2

or
f = Proc.new{|arg1| ... }
f.call arg1

Replace do/end with {/} if preferred.



I guess all of this sort of answers my earlier questions although
with the macro stuff it looks like it's going to take some time to
fully
grok. At least once I do I'll understand all those Paul Graham
essays :)


> > The equivalent in Clojure is effectively the same:
>
> > (defn with-open [stream f]
> >  (f stream))
>
> What about exception safety? Shouldn't that really be
>
> (defn with-open [stream f]
>   (try
>     (f stream)
>     (finally
>       (.close stream))))
>
> ?

In ruby you can do similarly with begin ... rescue ... ensure ....
end.




Should add, I thought the white paper from Clojure in Action was a
good read for someone who is newish to lisp.

Thanks folks,
Daniel Bush

James Reeves

unread,
Oct 26, 2009, 6:12:31 AM10/26/09
to Clojure
On Oct 25, 8:39 pm, Jarkko Oranen <chous...@gmail.com> wrote:
> Nitpick, but... The function closest to ruby's eval would be 'eval'.

This is true, but doesn't mean what I said isn't true also. The
closest thing to Ruby's eval is the Clojure eval function, and the
closest thing to Clojure's macros in Ruby is eval. A comparison of
language features isn't necessarily a bijection :)

- James

Perry Trolard

unread,
Oct 26, 2009, 10:51:14 AM10/26/09
to Clojure


> For example, the macro '->' changes this:
>
> (-> x (foo y) (bar z))
>
> Into this:
>
> (foo (bar x z) y)

Small correction. The result above should be:

(bar (foo x y) z)

user=> (macroexpand '(-> x (foo y) (bar z)))
(bar (clojure.core/-> x (foo y)) z)

Perry
Reply all
Reply to author
Forward
0 new messages