On Wed, Jul 6, 2011 at 3:14 AM, Konrad Hinsen
<konrad.hin...@fastmail.net> wrote:
> For a Clojure approach to this specific problem, see
>
>        http://clojure.github.com/clojure-contrib/complex-numbers-api.html
>
> which is based on the generic arithmetic from
>
>        http://clojure.github.com/clojure-contrib/generic.arithmetic-api.html
>
> and thus ultimately on multimethods. There is a performance penalty to such
> approaches, but it lets you formulate mathematical hierarchies much better
> than Java's OO style. Note that this approach is not based on types or on
> mathematical concepts such as fields, but only on operations: the real and
> imaginary parts of a complex number can be any value that supports
> arithemetic. This permits complex numbers with nonsensical elements, but I
> have not found this to be a problem in real life.

I wonder if generic math should become part of core for 1.4?
Multimethods of course are too slow for core arithmetic functions but
we have protocols now and when a protocol is implemented inline in a
datatype it runs as efficiently as any Java method call. The compiler
would still need to special-case +, -, etc. with primitive arguments
and those would retain the blinding speed of primitive arithmetic. The
biggest issue would be extending the protocol to boxed primitives,
BigInteger, and BigDecimal; extending a protocol to a class that
doesn't implement it inline isn't as efficient. BigInteger and
BigDecimal arithmetic, already slow, would get a tiny bit slower; it's
the impact on plain Integer, Double, etc. that could be worrying.

Can the compiler generate more efficient calls to protocol methods in
the presence of type hints or type inference sufficient to know the
correct protocol implementation? So if you (extend-protocol + Integer
...) during bootstrap, (+ some-integer some-other-integer) could be as
efficient as the current non-protocol-based + is when the compiler
knows the first argument is an Integer, whether from hinting it or by
some other means?

>> similarly with complex numbers, writing (complex x y) is very long
>> compared to xiy, or x +
>> iy.  Would it be possible to modify the reader so that, eg. 3i5 would
>> return a complex type?
>
> Yes, but I doubt you'd have much success lobbying for such a change. The
> tradeoff between cost (increased complexity of the reader) and utility
> (limited to a small number of users) doesn't look very promising. Moreover,
> my personal experience doing REPL math in Python shows that complex
> constants are pretty rare.

You can make it as simple as (i 3 5) by appropriately defining a
function or a macro i. (You might want to use definline so you can
both use i in HOFs, e.g. (map i list-of-real-parts
list-of-imaginary-parts), and get slightly more efficient bytecode out
of embedded complex values in code, with (i a b) as efficient at
generating a complex number as [a b] is at generating a vector when at
least one of a and b is not a literal constant; though (i 3 5) would
not be competitive with [3 5] for runtime speed without reader and
compiler(!) changes to embed complex numbers directly in code. When
you really need efficient access to a complex constant you'd thus need
to bind a non-dynamic global Var to the constant and then use it, e.g.

(defn slowish-math-routine [foo]
  (frobnicate foo (i 3 5))

might become

(def i35 (i 3 5))

(defn faster-math-routine [foo]
  (frobnicate foo i35))

to get the kind of speed of access you get with embedded vector
constants, embedded map constants, and suchlike.

(This is one more problem with Clojure's lack of ability to embed
arbitrary objects in eval'd sexps: it makes non-built-in data types
and even things like sorted-map not quite first class citizens because
you have to jump through extra hoops to make it possible to embed
constants of these types in code and get the same runtime efficiency
as with [x], {x y}, #{x}, etc. With the arbitrary object in code
capability efficient literals could be made even without read-table
access: #=(i 3 5) would embed a Complex in code given an appropriate
function i defined somewhere where it would be available at read time.
A bit ugly but not much worse than the existing literal syntax for
hashsets.)

>> So basically I'm stuck - short of writing a maths library from scratch
>> with pluggable types and dispatch by operation name, anyone got any
>> ideas?  Cheers for your help
>
> One idea (but not a simple one to implement) is to define a domain-specific
> language for maths (standard Clojure syntax but with different semantics)
> and compile it into standard Clojure using a set of macros.

Indeed; you can define some macro like (with-my-math body-goes-here)
that locally redefines some stuff, e.g.

(definline make-complex [x y] `(Complex. ~x ~y))

(defprotocol Addable
  (plus [this other]))

...

(defmacro with-my-math ...)

...

(with-my-math ...)

and the latter expanding to

(xlet
  [i make-complex
   + plus]
   - minus
   ...]
  ...)

But what is "xlet"? Plain let here would seem to work, but I'm not
sure the compiler would generate efficient bytecode for (+ x y) and
the like in that case versus when + is a non-dynamic global Var. If it
would, then xlet can just be let. Otherwise it may need to be some
kind of exception-safe temporary alter-var-root! that saves, changes,
and at scope exit restores the values of the named Vars. (Obviously
"binding" only works for dynamic vars, whose access is less efficient
still than a local var + in (+ x y), and we don't want dynamic scope
anyway, only lexical.)

If you can get a safe and reliable enough macrolet implementation for
Clojure, then you can use

(macrolet
  [(i [x y] `(make-complex ~x ~y))
   (+ [x y] `(plus ~x ~y))
   ...]
  ...)

and get good efficiency with nicely readable code. Otherwise you'd
have to resort to the var root thing, and I'm not 100% sure the
compiler would handle that ideally and generate for (xlet [+ my-+] (+
x y)) as efficient code as for (my-+ x y) (but it seems much less
likely if xlet = let, and certain to perform poorly, have side effects
down the line, and not work with non-dynamic vars if xlet = binding).

-- 
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to