On Apr 5, 2008, at 6:48 PM, Rich Hickey wrote:
I've added some syntactic sugar for host calls (as of SVN rev 793).
//lispy(.substring s 2 5) => (. s substring 2 5)//hosty(s.substring 2 5) => (. s substring 2 5)
> The first thing that has been bothering me has been the extra parens
> on method calls (vs Java):
>
> (. s (substring 2 5))
>
> So I've made them optional:
>
> (. s substring 2 5)
>
> Note that this creates the need to disambiguate between field access
> and no-args method calls. The only clash is when there is both a
> public field and a public no-arg method with the same name. These are
> resolved in favor of the method. Please let me know if you encounter
> one of these in practice.
In my "sql.clj" at clojure-contrib, one call doesn't work after
removing the parens. As driven by the "db-write" example code:
This works:
(defn execute-prepared-statement
"Executes a prepared statement with a sequence of parameter sets"
[con sql sets]
(with-open stmt (. con prepareStatement sql)
(doseq set sets
(doseq arg (map vector (iterate inc 1) set)
(. stmt setObject (arg 0) (arg 1)))
(. stmt (addBatch))) ; <<<<< with parens
(. stmt executeBatch)))
This compiles but fails at runtime:
(defn execute-prepared-statement
"Executes a prepared statement with a sequence of parameter sets"
[con sql sets]
(with-open stmt (. con prepareStatement sql)
(doseq set sets
(doseq arg (map vector (iterate inc 1) set)
(. stmt setObject (arg 0) (arg 1)))
(. stmt addBatch)) ; <<<<< without parens
(. stmt executeBatch)))
user=> (db-write)
java.sql.SQLException: count (0) < 1
java.lang.Exception: transaction rolled back
at clojure.fns.sql2_test.db_write__1281.invoke(sql2-test.clj:36)
at clojure.lang.AFn.applyToHelper(AFn.java:181)
at clojure.lang.AFn.applyTo(AFn.java:174)
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:2388)
at clojure.lang.Compiler.eval(Compiler.java:3443)
at clojure.lang.Repl.main(Repl.java:75)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor8.invoke(Unknown Source)
at
sun
.reflect
.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
25)
at java.lang.reflect.Method.invoke(Method.java:585)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:71)
at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:207)
at clojure.fns.sql2.execute_prepared_statement__1273.invoke(sql2.clj:
57)
at clojure.fns.sql2_test.db_write__1281.invoke(sql2-test.clj:17)
... 5 more
Caused by: java.sql.SQLException: count (0) < 1
at org.sqlite.DB.executeBatch(DB.java:220)
at org.sqlite.PrepStmt.executeBatch(PrepStmt.java:98)
... 12 more
user=>
It may be significant that PreparedStatement has an addBatch member
that takes no arguments (which I'm intending to call here):
http://java.sun.com/javase/6/docs/api/java/sql/PreparedStatement.html#addBatch()
while its superclass Statement has a member of the same name, but with
an argument:
http://java.sun.com/javase/6/docs/api/java/sql/Statement.html#addBatch(java.lang.String)
--Steve
In case it helps narrow down the problem for you, I found another
piece of code that doesn't work right when the parentheses are removed:
From ants.clj:
this works:
(defn animation [x]
(when running
(send-off *agent* #'animation))
(. panel (repaint)) <<<<< with parens, works
(Thread.sleep animation-sleep-ms)
nil)
this doesn't:
(defn animation [x]
(when running
(send-off *agent* #'animation))
(. panel repaint) <<<<< without parens, doesn't work
(Thread.sleep animation-sleep-ms)
nil)
--Steve
There seems to be a slight asymmetry between these two with regards to
literals. For example the lispy (.charAt "123" 0) works fine, but the
hosty ("123".charAt 0) produces:
java.lang.Exception: Unable to resolve symbol: .charAt in this context
clojure.lang.Compiler$CompilerException: NO_SOURCE_FILE:18: Unable to
resolve symbol: .charAt in this context
at clojure.lang.Compiler.analyzeSeq(Compiler.java:3455)
at clojure.lang.Compiler.analyze(Compiler.java:3339)
at clojure.lang.Compiler.analyze(Compiler.java:3314)
at clojure.lang.Compiler.eval(Compiler.java:3475)
at clojure.lang.Repl.main(Repl.java:75)
I can see that Compiler.macroexpand1 checks if the operator is a symbol
and thus does not recognize the second form. Maybe in case the operator
is not a symbol the first operand - if a symbol - should be checked as
well for the dot? Or maybe there is a deeper reason why this it not
allowed. The asymmetry just catched my eye.
-Toralf
Yeah. I didn't get that from your original email and just played a
little with the new candy not really thinking that it might be
restricted to symbols.
> It's not really syntax, or at least is not syntax that extends beyond
> the interpretation of a symbol/var as a macroexpansion trigger. I
> don't intend to extend it to support any-arbitrary-expression.member.
Fair enough.
-Toralf
I'm not trying to purposefully annoy you, you know. You invited
suggestions, and I responded at length the best I could. It's clear
this topic is something we'll have to agree to disagree on. But to at
least quickly address your reply:
On Mon, Apr 7, 2008 at 4:52 PM, Rich Hickey <richh...@gmail.com> wrote:
>
> You've defined an equivalence between extensibility and reader macros
> which is false. Reader macros are neither sufficient nor necessary for
> extensibility. I've enumerated several ways in which Clojure is
> extensible, some unique to Clojure (vs other Lisps). No language has
> everything - that's life.
I constrained myself to complaining about reader syntax as that's
where I've been running into walls. I can't say as of yet whether
there may be any other areas of Clojure that would turn out to not be
easily extensible. You've made the argument that there aren't (at
least if extending it in Java.)
> You've used Scheme as an example, a language whose many authors, over
> many revisions, have not considered reader macros important enough to
> include in the language, in spite of their obvious familiarity with
> the technique and any value it may have.
There are any number of useful things that the Scheme RnRS authors
haven't considered important enough to include in their historically
minimalist reports, including anything and everything that makes
Clojure a practical Lisp (say, a standard library - before R6RS,
anyway.) I don't intend to belabor the point, but when I talk about
Scheme systems I do *not* mean any abstract RnRS standard, nor would
you find many Schemers who would unduly constrain themselves so.
Please don't mischaracterize this.
> You say "code is data is code", another logical over-simplification,
> since while all code is data, not all data is code. The distinction
> between the use of the reader for data I/O and the use of the reader
> by the compiler is an important one, since the former can be supported
> without engendering the dialect-splintering of the latter.
>
> You keep asking 'what's the harm' without acknowledging any harms:
>
> It destroys interoperability
> Creates dialects
> Hinders the core language which has to leave room for extensions
> Breaks editor support
> When used by shared programs, is just a recipe for a mess
You did not address my essential point and key suggestion of
preventing these admitted harms on a level other than the
implementation level. Therefore I take it you disagree with that
suggestion. That's fine; I can't claim to know for sure that social
best practices would be sufficient to ensure interoperability in the
context of a Lisp dialect, even if such measures have proven
themselves on many other more conventional projects. I would elaborate
on this point at length, but let's not go there. Consider the question
buried.
> You also mentioned the extensibility of Ruby's runtime. And yet I keep
> reading articles about the problems of 'monkey-patching'. It's not all
> roses.
I will not get dragged into a Ruby discussion here except to say that
this is no more of an inherent "problem" in Ruby than it is in Lisp.
After all, "monkey-patching" is nothing more than the runtime dynamism
that provides for the possibility of redefining any existing function
(or method, in Ruby's case). It's not as if you *have* to always, day
in and day out, make use of the capability to, say, define 1 +1 = 3
just because you *can*. Experience brings restraint.
However, not finding that capability available hurts when you do need
it. As I've understood it from other threads on the list, Clojure
intends to (and does) explicitly support this capability - which just
happens to be known as "monkey-patching" among the less dynamic
languages - so any criticism of the facility will be equally
applicable to Clojure. Indeed if Clojure ever gets to be used on the
scale that Ruby presently is (and I do think it is the Lisp system
with at least the best shot at it, at present), I consider it
inevitable that we'll be seeing such articles from the folks who will
consider this and other advanced Lispy capabilities "dangerous"
features to be prevented by fiat instead of as a matter of
case-specific good judgment.
> Your 2 use cases - embedded ':' in symbols, and hex numbers, are best
> handled by other means. Multiple number formats are going to be
> supported (patch already submitted), but will follow moving to
> standard boxed numbers (in-progress). Embedded ':'s are currently
> disallowed in a conservative restriction put in place when I wasn't
> sure I wouldn't be using them for namespaces, a restriction that might
> be eased if you ask nicely :)
OK, makes sense. Hopefully this could indeed be implemented, as it
would remove the immediate need to go and write a reader of my own or
revert to Scheme for the use cases that I need this for - so pretty
please, consider this a feature request.
> You perhaps haven't been here long enough to see that language
> features advocated by users, and not that important to me personally,
> e.g. regex, have been added. So it's not just about what I want, but
> it is of a single vision.
>
> Bottom line(s):
>
> - Clojure is very extensible - I'll not have it labeled otherwise
> because of a lack of reader macros.
>
> - Clojure does not take the 'By default, allow' approach. For an
> experiment with that paradigm - try Arc.
>
> - Clojure may never have user-extensible reader macros that impact
> the input to the compiler - if that is a must have for people, I
> recommend CL, which has a standard, portable way to do that.
>
> IMO, language design is equally about leaving things out as putting
> them in. Where's the design in a free-for-all? I feel strongly that a
> programming language is as much about communicating with other users
> as it is communicating with the computer, and thus interoperability is
> not a "constraint", it's an objective. If we each had our own language
> we could only talk to ourselves.
That's fine. I couldn't agree more regarding the importance of
consistently saying 'no', though I, of course, don't enjoy being at
the butt end of such a decree. I do feel the present matter at hand is
a false dichotomy - as I've outlined. But it's your language, your
vision, and I'm not going to tell you how to run your show. I've made
my case to such an extent that it should be made, here, and further
irritating you regarding my own take on "extensibility" etc. is
unlikely to yield any positive results.
If I do need some capability that you're not willing to include in the
language, it's not the end of the world - being a Lisp, Clojure does
make it comparatively easy to Greenspun my own reader, or anything
else for that matter. We can consider the matter closed - though maybe
it could be revisited in a friendly way over beer sometime, if I
should happen to find myself on your side of the big pond.
Arto
--
Arto Bendiken | http://bendiken.net/
Is it possible to extend dispatch function as well? Say, I'm using a
function that dispatches methods based on the contents of a data
structure. Now, if the data structure evolves, the dispatch function
must be extended to cover new additions.
The only solution that comes to my mind is to tag the data structure
with a ":type" field and dispatch methods using this field. However,
in this case, I need to implement the dispatch code twice - once in
"defmulti" and once in the constructor of the data structure. The
final object is also not very convenient to use. If the original data
structure was a list, it must be wrapped with a map before use etc.
Cheers,
-r.
Christophe
Thank you guys for all suggestions. I particularly like the idea of
putting type tags in metadata. I haven't thought about this - perhaps
because I considered metadata, like exceptions, a tool that should not
be used for the flow control (in general).
> Using a multimethod as a dispatch function is a fine idea. While it
> still doesn't support swapping out, it does allow for some
> extensibility in how one finds the dispatch value.
I think the "dispatch multimethods" will have same limitations as the
original multimethod we wanted to extend. Besides, I would rather
avoid piling code in the dispatch function for performance reasons.
Another question:
How does "recur" behaves inside a multimethod? Is there any way of
looping through the multimethod (together with its dispatch function)
iteratively?
Cheers!
-r.
Similarly, I would nice to have "recur" aware of argument matching in
regular functions:
user=> (defn a
([]
(a 1))
([n]
(recur)))
java.lang.IllegalArgumentException: Mismatched argument count to
recur, expected: 1 args, got: 0
-r.
This example produces a NPE in InstanceFieldExpr.emit():
(defn f []
(let [u (new java.net.URL "http://www.google.com")
c (.openConnection u)]
(.getResponseCode c)))
The peculiarity of this code is that the getResponseCode method is part
of a subclass of URLConnection (i.e. HttpURLConnection) which is
returned by openConnection. Thus the HostExpr.Parser.parse() method
considers this to be an instance field expression because it can not
find the method in URLConnection. This makes me feel a bit concerned
about the ambiguity introduced by the new forms regarding field vs.
method access.
-Toralf
I briefly tried the "hosty" variant, but I'm now back to using the .
macro. I find it clearer as it highlights the non-lispy parts of the
code - '(. ' sticks out against other macros/functions - which is
particularly relevant in Clojure as you have to pay attention to lazy
evaluation whenever anything might have side effects, and Java functions
often fall into this category... When I end up using a simple Java
function more than once I typically wrap it in a Clojure function anyway.
~phil
--Chouser
Christophe
Not sure I like the optional parens on method calls using dot:
(. s substring 2 5)
as long as we have the lispy syntax that doesn't need the parens:
(.substring s 2 5)
The lack of parens on the first version seems more confusing than anything.
Ozzi
(Color:red)
You could also have : figure out getters, so that if foo had a getBar
method, (foo:bar) would work.
You could get real crazy and eliminate the parens, too.
(print foo:bar)
instead of
(print (.getBar foo))
Perhaps it's just useless (and confusing) sugar after all, though.
Ozzi
FWIW, Kawa uses a similar colon notation[0] for field and static
method access.
:dudley
I like the new syntax for "new" a lot. I gather you've considered an
unadorned Classname as the first item in a list as a trigger for an
implicit "new" and rejected it. If that could work it would be a
natural.
--Steve
(-> urlstr URL. .openConnection .getContent InputStreamReader. BufferedReader.)
--Chouser
It is probably my habits from reading a lot of Java code.
I found the lispy was different enough from Java that it stand out and
did not turn on my Java pattern matching in my brain.
The hosty one kept turning on little alarms, I could not get used to
it.
Regards,
--
,`,`,`,`,`,`,`,`,`,`,`,`,
,`,`,`,` @ `,`,`,` .com ,
, Dimitry Gashinsky ,`,`,
,`,`,`,`,`,`,`,`,`,`,`,`,