开发者

Is it a bad idea to put functions in Clojure maps, like in JavaScript?

I'm new to Clojure and finding my sea legs. I was wondering whether it is considered good or bad practice, from a func开发者_开发知识库tional programming standpoint, to put functions in Clojure maps and then pass those maps around like quasi-objects, as is often done in JavaScript. Explanations will also be appreciated.


Do it, if it makes your code shorter or easier to understand, or test, or debug.

Or if you'd just like to. Trust your judgement.


Clojure's multimethods are essentially maps of functions, so no, it's not a bad idea at all.


It would be bad because of several reasons:

  1. It's not necessary - In JavaScript, we are doing it to use simple maps as Java-like objects. In Clojure, if you really want to do so, you can use actual Java object via Java interop.
  2. Whether you are putting functions in maps or you are using real Java object, you are doing object oriented programming in a language environment which was designed for functional programming. First concern that comes with it is that your code just would't fit in. It would look strange, clumsy and maybe even too complicated in Clojure. At this point, I'm not saying that OOP is necessarily bad, just that it is better to write OOP in languages which were designed for it.
  3. The most important point against such coding style is that OOP-like program would inevitably rely on objects that have some state, and their member functions (kinda like methods) that alter that state. If you're using state, then your functions aren't pure and you can't use all the functional goodness like easy parallelization.

Long story short, if you're using Clojure to do OOP, you'll only get annoyed. You can do OOP more easily in tons of other languages, like say Groovy. If you do want to use Clojure do it the functional way.

So far I wrote not to do it and why not to do it. Now you might ask me: So, how should I write functions in Clojure?

Write functions that take your data structure (i.e. a map, list, object... whatever) as a parameter. So, instead of:

 foo.bar();

You would define it like this:

 (defn bar [foo]
   ;stuff
   ) 

And call it like this:

  (bar foo)

Now, for a pure function bar, foo object is unchanged after the function evaluation, and you don't have a shared state of the object to worry about if you decide to parallelize your code, which you would have if you were doing things the OOP way.

Also, it may look like a small difference, but note that the bar function definition is an entity entirely independent of the data structure foo. Also, foo contains only the data, not behavior - all behavior is in the function. This separation gives you a much greater freedom when coding.


It works - and in some sense it's natural if you consider functions as first-class objects in the language (as all functional programmers should!)

However - it needs real care because what you're basically doing is interleaving code with data. It's rather like mixing your data model with presentation code in MVC. Yes, there may be some situations where it makes sense but the general principle would be to avoid it.

The style I'm slowly converging towards after about a year of Clojure is:

  • A few top-level refs / atoms / vars for clear management mutable state (kudos to Rich Hickey!)
  • Large, nested immutable data structures within the top-level references
  • Pure functions to handle pretty much all processing
  • A few functions ending in ! for those nasty side effects, used sparingly
  • Lots and lots of tests! This is really important because with dynamic typing and very flexible data structures you need to test pretty much all your assumptions
  • Java (sadly!) for optimising performance sensitive sections of code, usually done by defining a lightweight, immutable Java class which works well in Clojure data structures

This solves most things under the sun, the one thing I'm still trying to figure out is the best way to create objects with highly polymorphic behaviour. Main options I can see are:

  • Use protocols with deftype / defrecord - high performance, idiomatic Clojure, but you only get single dispatch on type which is insufficient for some highly polymorphic situations
  • Create functions that behave in a polymorphic way by lots of macros / higher order function composition - works, but can very very convoluted / complex to maintain
  • Put functions inside maps! Yes it feels wrong, but it works!

Still haven't worked out which way to go..... but I have a hunch that functions may sneak into maps a bit more in the future....


If you really need to do object oriented programming in Clojure there are a couple of ways to do so.

The first way is to use the deftype, defrecord and defprotocol family of macros.

The second way is to use multimethods, combined with a map or record type for data storage.

The third way is to use the universal design pattern as outlined by Steve Yegge, or a more Clojure specific introduction can be found in Chris Houser and Michael Fogus' book The Joy of Clojure.

The third approach is very similar to what you would expect to see in JavaScript, and the first approach, while not thought of as OOP in a traditional sense, is the most common in "modern" idiomatic Clojure.


Namespaces are literally maps of functions. It's much more straightforward to use them to organize your functions. However, if you run into a limitation of namespaces for your use case, you could consider putting functions in a map.

As usual when going off the beaten track, just be sure to think hard about why you're not going the obvious route.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜