I have been converting the examples from "Practical Common Lisp" into
Clojure. A big part of this is switching from imperative to functional
mindset. You might want to look at Chapters 3, 16, and 17.
http://blog.thinkrelevance.com/2008/9/16/pcl-clojure
Cheers,
Stuart
> So, I understand as follows:
> - OOP: keeping objects, which has states and methods.
> methods are encapuslated to the corresponding object.
> - FP: keeping objects(or structs or variables, whatever it is called)
> and functions on the objects.
> functions does not encapsulated to specific objects, for some
> reasons.
>
> Other characteristics such as function as first-order data structure
> and
> enforcing functional way (discourated side effects and immutable
> variables)
> can be important, but not in modeling the world.
I think the difference is more fundamental than what you describe. On
the other hand, I don't think that OOP and FP are contradictory.
Here is my view of things:
OOP = data abstraction. The program structure is defined by the data
that the program works on. Important data structures are identified
and implemented together with their associated operations as classes.
FP = algorithmic abstraction. The program structure is defined by the
algorithms that are used. Important algorithmic patterns are
identified and implemented as functions that take functional
arguments, or as macros. Functional arguments make it possible to
insert specific actions into a skeleton.
There are additional properties typically associated with OOP
(polymorphism, inheritance, ...) and FP (referential transparency,
immutable data structures, ...), but I think the approch to program
structure is the aspect where the two methods differ most. On the
other hand, there is no reason not to use both data abstraction and
algorithmic abstraction in a program. In that sense OOP and FP are
orthogonal and combinable.
As an illustration of the two approaches, consider a program to sort
data. In OOP, one would define an abstract class "comparable" with a
method "sort" that works by calling methods such as "greater" and
"equal" implemented in concrete subclasses. In FP, one would write a
function "sort" that takes as arguments a list of things to sort plus
a function to do the comparisons. At the top level of the program,
you'd see "interface comparable" in the OOP version and "function
sort" in the FP version. A mixed OOP-FP program might call the FP
function "sort" and pass the method "compare" of a subclass of
"comparable" as the comparison function.
Konrad.
Even the current Java libraries and object model belie this comparison.
An array of intrisically comparable instances (those that "implement
Comparable") can be sorted without supplying a Comparator. But an array
of arbitrary Objects can be sorted in arbitrary and flexible ways by
supplying a Comparable that accepts the types submitted to it.
> Konrad.
Randall Schulz
> And as an effect of not forcing the sorting logic into some class,
> one can easily sort the same data in different ways in the FP style.
> While implementing an interface Comparable defines *one* way
> of sorting, the FP style separates the functions from the data. So
> it is eg. easily possible to sort a list of email objects by subject,
> author or date, or any combination thereof.
Sorting is indeed an example where the FP approach is superior. But
then, it is not representative in size and complexity of a typical
program or library. One could easily cite other examples where OOP
would be the better choice. My ideal language would support both as
much as possible.
An example of where Clojure lacks OO features, in my opinion, is your
lazy-map library. It provides an additional implementation for an
existing interface, which is a typical OO approach, and a very useful
one. But although all your code is written Clojure, it looks like a
kludge in having to use a feature (genclass) intended for Java
compatibility and forcing an unnatural file structure (one file for
the methods, one for the code generation, and one for the end-user
wrapper) on the implementation.
Konrad.
i think that is a good point. there is not a single succinct
definition of OO when one looks at the various ways it has been
actually implemented in languages: Smalltalk is not the same thing as
Java. I think the ways in which they differ can have a serious impact
on what it means in the end to "be" OO, especially when contrasting
with a different paradigm (be it FP or Declarative or Procedural or
whathaveyou).
sincerely.
> Of course sometimes you need side effects in order to accomplish
> useful work, and a good FP language will provide abstractions that
> help you limit side effects and reason about them. Clojure does this
> by means of refs, agents, and transactional updates. refs are
> different than regular values so that you know they are mutable.
Vars are also mutable, even if that mutability is local to a thread.
I guess one could write state-modifying code in Clojure just like in
most imperative languages, it is merely discouraged and made hard to
hide. That looks like a very reasonable compromise to me.
> An object-oriented program may also use FP. OOP and FP are not
> opposites. However, a lot of OO programming these days involves
> changing the state of opaque objects (therefore, not purely
> functional) via member functions. I've heard this characterized as
> "the new spaghetti code" because it's hard to reason about thread-
> safety when objects are being modified all the time, and when these
> modifications aren't syntactically obvious.
Even in the absence of threads, changing state can be a cause of
trouble, in particular if it is well hidden under a few layers of
nice-looking APIs.
On the other hand, I am not sure that all important algorithms
already have purely functional equivalents that are sufficiently
efficient for real life. Is there an efficient purely functional
algorithm for matrix inversion, for example?
Konrad.
Am 04.11.2008 um 16:06 schrieb Konrad Hinsen:
> An example of where Clojure lacks OO features, in my opinion, is your
> lazy-map library. It provides an additional implementation for an
> existing interface, which is a typical OO approach, and a very useful
> one. But although all your code is written Clojure, it looks like a
> kludge in having to use a feature (genclass) intended for Java
> compatibility and forcing an unnatural file structure (one file for
> the methods, one for the code generation, and one for the end-user
> wrapper) on the implementation.
I disagree. Clojure brings everything needed: multimethods. One defines
a set of multimethods (the interface) and different arguments may have
different implementations.
Inheritance can also be replaced with delegation without problems. And
in fact lazy-map does not inherit from any maptype. Instead it delegates
to another instance without caring for whether it's a struct map, hash
map or sorted map. With inheritance each of this types would have needed
an own lazy-x-map class. This is again some style of functional OO
programming.
Suppose get, contains?, etc. were multimethods. I would not have needed
gen-class. I just would register my own implementations for the lazy-
map.
But the problem is: Clojure's map interface is not implemented with
multimethods. They have a Java side. If I want to provide a drop-in
replacement I have to also serve that side. Hence I need gen-class.
Or Java itself for that matter in case gen-class is too kludgy.
I don't think that Clojure is that bad for object-oriented programming.
It just looks a bit different than usual.
Sincerely
Meikel
> Suppose get, contains?, etc. were multimethods. I would not have
> needed
> gen-class. I just would register my own implementations for the
> lazy-map.
> But the problem is: Clojure's map interface is not implemented with
> multimethods. They have a Java side.
That's exactly my point. Multimethods may well be sufficient or even
superior for implementing OO concepts useful in Clojure. We will see
when someone actually uses them this way (or has it already
happened?). But as long as the most important interfaces (sequences,
maps, numbers), which happen to be the ones also used by the built-in
types, don't use the same mechanism, there will always be first-class
and second-class citizens in Clojure's datatype universe.
Konrad.
> Long answer: SISAL is an example of a functional parallel language
Ah, right, there was SISAL... unfortunately long forgotten.
> One could express solving linear systems (which is what I presume you
> mean by "matrix inversion," unless you really want the entries of the
> inverse) in a purely functional way using a language like SISAL with a
> compiler that optimizes away temporaries, and the resulting
Could one? That was actually the core of my question. Of course HPC
applications would require smart compilers that, but first of all
there must be a purely functional algorithm that can be transformed
automatically into an efficient one. Do you know any references to
such algorithms in linear algebra?
Of course, HPC applications dealing with large data sets would always
want algorithms that modify matrices in place instead of allocating
more memory. I don't expect a purely OO HPC world any time soon. But
it would still be interesting to know how many of the traditional CPU-
hungry algorithms already have known efficient functional equivalents.
> That was basically why SISAL didn't take off at Livermore. HPC coders
> do, however, write a lot of control code, scripts, etc. that can
> benefit from FP. This is why I follow Clojure (for example).
Me too. HPC is only one aspect of my work. And I think languages like
Clojure can be useful for generating specialized HPC code as well,
just like FFTW uses Caml code for generating C routines.
Konrad.
> I don't think that there are first-class and second-class citizens.
> They just have a different aspect.
Right, one can't say one is superior to the other. There are just two
separate worlds, each having its own characteristics.
> Having pure Clojure "classes" with
> multimethods might well be integrated and nice, but you get problems
> when you have to interface to Java. On the other hand using Java
> classes gives you easy interface, but you have to live with gen-class.
But does gen-class have to look the way it does? Couldn't the same
functionality be provided in a way that looks more like a proper part
of the language?
> What would interesting, is the question, whether both approaches
> could be combined. We make a Java class (via gen-class), which
> gets a default implementation for each method, which references
> a similar named multimethod instead of the Class-methodName.
That sounds like an interesting idea.
> For the built-in datatypes: I implemented nth and get as multimethods
> (as some kind of proof-of-concept), but Rich was not very interested
> due to performance issues and the fact, that the datatypes are used
> internally in very low-level areas.
I can understand performance arguments, of course. And I don't really
want to modify the built-in types in any way, I just want to be able
to use the same interfaces in completely independent datatype
implementations.
The current implementation of nth provides a special implementation
for each kind of datatype that it knows about, and fails for unknown
types. Shouldn't it be possible to add a default case at the end that
calls a Clojure multimethod? That shouldn't have any performance
impact on the built-in datatypes.
> I don't really follow the argument about monkey patching. I think
> it's not monkey patching at all, since
> the multimethods only define the interface.
I agree. It's no more monkey-patching than any use of multimethods
would be monkey-patching.
Konrad.
> and replace copies with destructive writes. I haven't seen a purely
> functional formulation of LU factorization but it could be done
> without too much trouble. Of course there's no reason to go through
> that effort because people spend so much time optimizing LU and its
> constituent components that you would be better off reusing their
> work.
For the immediate future, yes. But with changing computer
architectures, the existing algorithms and routines may lose much of
their interest in the future.
> To me the more interesting and rewarding task is to figure out how to
> splice existing HPC libraries into a functional framework, without
> losing the ability to reason functionally about the components.
Unfortunately, it is already a bit of a pain to link existing HPC
libraries (written in Fortran, C, or C++) to functional code in any
decent language. Clojure won't help there, as there are still very
few HPC libraries for the JVM and JNI adds too much of an overhead.
> Definitely! We've got at least one fellow here who uses Common Lisp
> to generate stencil codes. He's been thinking about switching to
> Clojure ever since he and I worked on a thorny Lisp problem
> together ;-)
I am looking forward to a nice Clojure library then :-)
Konrad.
> Does the JNI require copy-in / copy-out for arrays of floating-point
> numbers?
The JNI requires the C code to call JNI routines for converting the
Java array to a native array and back. Whether or not a copy is made
depends on the JNI implementation.
> There are a number of Python-based projects to do what you mentioned
> (link HPC libraries in Fortran or C to a higher-level language), so
> it's not impossible. It's just a lot of tedious work for some unlucky
> coders.
Python has a much nicer C interface than Java. The NumPy library
provides an array implementation whose underlying data structure can
be used directly as a C/Fortran array. No copying, no conversion, and
there are lots of tools that do much of the unpleasant work.
Konrad.
> My expectation is these:
> 1) For C10K problem (or C100K), application must not use
> native threads. Big stack size means low concurrency
Hi,
I assume you mean 'new native thread per request' is bad for CnK.
Clojure's thread-pooling is not very different from the N:M threading
model that Erlang-OTP/Yaws uses, in a general sense.
> 4) At the same time, there must be ways for connecting conceptual
> gap between 2) and 3). In other words, the way for suspending current
> execution of function, saving current execution snapshot
> (normally native thread stack, but may be different),
> and switching to other functions are needed when the request for I/O
> should be blocked. It's important that the size of current execution
> snapshot should be small, since it determine the degree of
> concurrency.
Sounds like you're looking for first-class continuations here.You
won't find that on any JVM language, there's no JVM support for them.
(It is true that JVM-based Scheme languages have continuations, but
they are severely limited by the constraints of the JVM).
If you really believe you need this, you might be interested in
Termite, an Erlang-style actor system written for Gambit Scheme (which
has serializable first-class continuations and very lightweight
threads, but no SMP support if I remember correctly).
An alternative would be to use non-blocking I/O multiplexing -- an
event-driven model like you would write with select() or epoll(). I
don't know the Java universe very well, but I know JVM has
asynchronous I/O, so this must be an available option. Writing such
programs can sometimes lead to hard-to-understand, fragmented
application code, but Clojure's flexible syntax might be able to help
"unify" the fragments into a more readable form.
Best,
Graham