notes-computer-programming-programmingLanguageDesign-prosAndCons-clojure

links

tutorials


toread:

http://www.tbray.org/ongoing/When/200x/2009/12/01/Clojure-Theses


...

I have discovered that for me, the #1 factor that determines my programming productivity is the set of data structures that are built-in to the language and are easy to work with. ..." You can write vector-based algorithms in Racket, but they look verbose and ugly. Which would you rather read: a[i]+=3 or (vector-set! a i (+ (vector-ref a i) 3)) ? ...

Racket goes further than most Scheme implementations in providing built-in data structures. It also offers, for example, hash tables (and recently sets were added). But the interface for interacting with hash tables is a total mess. The literals for expressing hash tables use dotted pairs. If you want to construct hash tables using the for/hash syntax, you need to use "values". If you want to iterate through all the key/value pairs of a hash table, it would be nice if there were an easy way to recursively process the sequence of key/value pairs the way you would process a list. Unfortunately, Racket provides no built-in lazy list/stream, so you'd need to realize the entire list. But even if that's what you'd want to do, Racket doesn't provide a built-in function to give you back the list of keys, values or pairs in a hash table. Instead, you're encouraged to iterate through the pairs using an idiosyncratic version of its for construct, using a specific deconstructing pattern match style to capture the sequence of key/value pairs that is used nowhere else in Racket. (Speaking of for loops, why on earth did they decide to make the parallel for loop the common behavior, and require a longer name (for*) for the more useful nested loop version?) Put simply, using hash tables in Racket is frequently awkward and filled with idiosyncracies that are hard to remember. ... There are downloadable libraries that offer an assortment of other data structures, but since these libraries are made by a variety of individuals, and ported from a variety of other Scheme implementations, the interfaces for interacting with those data structures are even more inconsistent than the built-ins, which are already far from ideal. ... "

" Clojure gets data structures right. There's a good assortment of collection types built in: lists, lazy lists, vectors, hash tables, sets, sorted hash tables, sorted sets, and queues. ALL the built-in data structures are persistent/immutable. ... Vectors are just as easy to work with as lists. Equality is simplified. Everything can be used as a key in a hash table.

Data structures in Clojure get a little bit of syntactic support. Not a tremendous amount, but every little bit helps. Code is a little easier to read when [1 2 3] stands out as a vector, or {:a 1, :b 2, :c 3} stands out as a hash table. Lookups are a bit more terse than in Racket -- (v 0) instead of (vector-ref v 0). Hash tables are sufficiently lightweight in Clojure that you can use them where you'd use Racket's structs defined with define-struct, and then use one consistent lookup syntax rather than type-specific accessors (e.g., (:age person) rather than (person-age person)). This gets to be more important as you deal with structures within structures, which can quickly get unwieldy in Racket, but is easy enough in Clojure using -> or get-in. Also, by representing structured data in Clojure as a hash table, you can easily create non-destructive updates of your "objects" with certain fields changed. Again, this works just as well with nested data. (Racket structs may offer immutable updates in future versions, but none of the proposals I've seen address the issue of updating nested structured data.) Furthermore, Clojure's associative update function (assoc) can handle multiple updates in one function call -- contrast (assoc h :a 1 :b 2) with (hash-set (hash-set h 'a 1) 'b 2).

Even better, the process for iterating through any of these collections is consistent. All of Clojure's collections can be treated as if they were a list, and you can write algorithms to traverse them using the same pattern of empty?/first/rest that you'd use on a list. This means that all the powerful higher-order functions like map/filter/reduce work just as well on a vector as a list. You can also create a new collection type, and hook into the built-in sequence interface, and all the built-in sequencing functions will automatically work just as well for your collection.

Although the sequencing functions work on any collection, they generally produce lazy lists, which means you can use good old recursion to solve many of the same problems you'd tackle with for/break or while/break in other languages. ... A polymorphic approach is applied through most of Clojure's design. assoc works on vectors, hash tables, sorted hash tables, and any other "associative" collection. ... "

" Data structures in Clojure get a little bit of syntactic support. Not a tremendous amount, but every little bit helps. Code is a little easier to read when [1 2 3] stands out as a vector, or {:a 1, :b 2, :c 3} stands out as a hash table. Lookups are a bit more terse than in Racket -- (v 0) instead of (vector-ref v 0). Hash tables are sufficiently lightweight in Clojure that you can use them where you'd use Racket's structs defined with define-struct, and then use one consistent lookup syntax rather than type-specific accessors (e.g., (:age person) rather than (person-age person)). This gets to be more important as you deal with structures within structures, which can quickly get unwieldy in Racket, but is easy enough in Clojure using -> or get-in. Also, by representing structured data in Clojure as a hash table, you can easily create non-destructive updates of your "objects" with certain fields changed. Again, this works just as well with nested data. (Racket structs may offer immutable updates in future versions, but none of the proposals I've seen address the issue of updating nested structured data.) Furthermore, Clojure's associative update function (assoc) can handle multiple updates in one function call -- contrast (assoc h :a 1 :b 2) with (hash-set (hash-set h 'a 1) 'b 2). "

"

Debugging is difficult -- every error generates a ridiculously long stack trace that lists 500 Java functions along with (maybe, if you're lucky) the actual Clojure function where things went awry. Many of Clojure's core functions are written with a philosophy that they make no guarantees what they do with bad input. They might error, or they might just return some spurious answer that causes something to blow up far far away from the true origin of the problem.

Clojure inherits numerous limitations and idiosyncracies from Java. No tail-call optimization, no continuations. Methods are not true closures, and can't be passed directly to higher-order functions. Proliferation of nil and null pointer exceptions. Slow numeric performance. Compromises with the way hashing and equality works for certain things to achieve Java compatibility. Slow startup time. "

" Clojure has a number of cool new ideas, but many of them are unproven, and only time will tell whether they are truly valuable. Some people get excited about these features, but I feel fairly neutral about them until they are more road-tested. For example:

Clojure's STM implementation - seems promising, but some reports suggest that under certain contention scenarios, longer transactions never complete because they keep getting preempted by shorter transactions. agents - if the agent can't keep up with the requests demanded of it, the agent's "mailbox" will eventually exhaust all resources. Perhaps this approach is too brittle for real-world development? vars - provides thread isolation, but interacts poorly with the whole lazy sequence paradigm that Clojure is built around. multimethods - Clojure provides a multimethod system that is far simpler than, say CLOS, but it requires you to explicitly choose preferences when there are inheritance conflicts, and early reports suggest that this limits extensibility. protocols - This is an interesting variation on "interfaces", but it's not clear how easy it will be to compose implementations out of partial, default implementations. transients - Nice idea for speeding up single-threaded use of persistent data structures. Transients don't respond to all the same interfaces as their persistent counterparts, though, limiting their usefulness. Transients are already being rethought and are likely to be reworked into something new. "


" I would advise checking out the problems at http://www.4clojure.com/ over the koans. You will definitely find some challenging problems. The nice thing about these is that you can look at how other users all solving the problems, which helps to convey a sense of style. I haven't done a problem in awhile, but I oftentimes gotten something out of looking at solutions from 'amalloy' and 'chouser' in particular. Of course, you can also check out my own ('gajomi') solutions :). "

" The speed difference between Scala and Clojure has less to do with the static/dynamic gap than the fact that Clojure relies upon reflection by default. However, once you identify your bottlenecks, adding type hints to remove reflection is pretty easy. "


"

pros

 For many years, Python set the standard for me, offering easy syntax to manipulate extensible arrays (called lists in Python), hash tables (called dictionaries in Python), tuples (an immutable collection that can serve as keys in a hash table), and in recent versions of Python, sets (mutable and immutable), heaps, and queues.... You can write vector-based algorithms in Racket, but they look verbose and ugly. Which would you rather read: a[i]+=3 or (vector-set! a i (+ (vector-ref a i) 3)) ? But if you can get past the more verbose syntax, there's still the fundamental issue that all the patterns change when you move from using a list to a vector. The way of working with them is so fundamentally different that there is no easy way to change code from using one to another.

...

the interface for interacting with hash tables is a total mess. The literals for expressing hash tables use dotted pairs. If you want to construct hash tables using the for/hash syntax, you need to use "values". If you want to iterate through all the key/value pairs of a hash table, it would be nice if there were an easy way to recursively process the sequence of key/value pairs the way you would process a list. Unfortunately, Racket provides no built-in lazy list/stream, so you'd need to realize the entire list. But even if that's what you'd want to do, Racket doesn't provide a built-in function to give you back the list of keys, values or pairs in a hash table. Instead, you're encouraged to iterate through the pairs using an idiosyncratic version of its for construct, using a specific deconstructing pattern match style to capture the sequence of key/value pairs that is used nowhere else in Racket. (Speaking of for loops, why on earth did they decide to make the parallel for loop the common behavior, and require a longer name (for*) for the more useful nested loop version?) Put simply, using hash tables in Racket is frequently awkward and filled with idiosyncracies that are hard to remember. ...

There are downloadable libraries that offer an assortment of other data structures, but since these libraries are made by a variety of individuals, and ported from a variety of other Scheme implementations, the interfaces for interacting with those data structures are even more inconsistent than the built-ins, which are already far from ideal.

...

Clojure gets data structures right. There's a good assortment of collection types built in: lists, lazy lists, vectors, hash tables, sets, sorted hash tables, sorted sets, and queues. ALL the built-in data structures are persistent/immutable. That's right, even the *vectors* are persistent. For my work, persistent vectors are a huge asset, and now that I've experienced them in Clojure, I'm frustrated with any language that doesn't offer a similar data structure (and very few do). The consistency of working only with persistent structures is a big deal -- it means you use the exact same patterns and idioms to work with all the structures. Vectors are just as easy to work with as lists. Equality is simplified. Everything can be used as a key in a hash table.

Data structures in Clojure get a little bit of syntactic support. Not a tremendous amount, but every little bit helps. Code is a little easier to read when [1 2 3] stands out as a vector, or {:a 1, :b 2, :c 3} stands out as a hash table. Lookups are a bit more terse than in Racket -- (v 0) instead of (vector-ref v 0). Hash tables are sufficiently lightweight in Clojure that you can use them where you'd use Racket's structs defined with define-struct, and then use one consistent lookup syntax rather than type-specific accessors (e.g., (:age person) rather than (person-age person)). This gets to be more important as you deal with structures within structures, which can quickly get unwieldy in Racket, but is easy enough in Clojure using -> or get-in. Also, by representing structured data in Clojure as a hash table, you can easily create non-destructive updates of your "objects" with certain fields changed. Again, this works just as well with nested data. (Racket structs may offer immutable updates in future versions, but none of the proposals I've seen address the issue of updating nested structured data.) Furthermore, Clojure's associative update function (assoc) can handle multiple updates in one function call -- contrast (assoc h :a 1 :b 2) with (hash-set (hash-set h 'a 1) 'b 2).

Even better, the process for iterating through any of these collections is consistent. All of Clojure's collections can be treated as if they were a list, and you can write algorithms to traverse them using the same pattern of empty?/first/rest that you'd use on a list. This means that all the powerful higher-order functions like map/filter/reduce work just as well on a vector as a list. You can also create a new collection type, and hook into the built-in sequence interface, and all the built-in sequencing functions will automatically work just as well for your collection.

...

Although the sequencing functions work on any collection, they generally produce lazy lists, which means you can use good old recursion to solve many of the same problems you'd tackle with for/break or while/break in other languages. For example, (first (filter even? coll)) will give you the first even number in your collection ... "

" assoc works on vectors, hash tables, sorted hash tables, and any other "associative" collection. And again, you can hook into this with custom collections. This is far easier to remember (and more concise to write) than the proliferation of vector-set, hash-set, etc. you'd find in Racket. "

"

Summary:

    Clojure provides a full complement of (immutable!) data structures you need for everyday programming and a bit of syntactic support for making those manipulations more concise and pleasant.
    All of the collections are manipulated by a small number of polymorphic functions that are easy to remember and use.
    Traversals over all collections are uniformly accomplished by a sequence abstraction that works like a lazy list, which means that Clojure's higher order sequence functions also apply to all collections."

cons

"

The IDEs available for Clojure all have significant drawbacks. You can get work done in them, but any of the IDEs will probably be a disappointment relative to what you're used to from other languages (including Racket).

Debugging is difficult -- every error generates a ridiculously long stack trace that lists 500 Java functions along with (maybe, if you're lucky)... Many of Clojure's core functions are written with a philosophy that they make no guarantees what they do with bad input. ...

Clojure inherits numerous limitations and idiosyncracies from Java. No tail-call optimization, no continuations. Methods are not true closures, and can't be passed directly to higher-order functions. Proliferation of nil and null pointer exceptions. Slow numeric performance. Compromises with the way hashing and equality works for certain things to achieve Java compatibility. Slow startup time.

"

misc opinions

"

Clojure has a number of cool new ideas, but many of them are unproven, and only time will tell whether they are truly valuable. Some people get excited about these features, but I feel fairly neutral about them until they are more road-tested. For example:

    Clojure's STM implementation - seems promising, but some reports suggest that under certain contention scenarios, longer transactions never complete because they keep getting preempted by shorter transactions.
    agents - if the agent can't keep up with the requests demanded of it, the agent's "mailbox" will eventually exhaust all resources. Perhaps this approach is too brittle for real-world development?
    vars - provides thread isolation, but interacts poorly with the whole lazy sequence paradigm that Clojure is built around.
    multimethods - Clojure provides a multimethod system that is far simpler than, say CLOS, but it requires you to explicitly choose preferences when there are inheritance conflicts, and early reports suggest that this limits extensibility.
    protocols - This is an interesting variation on "interfaces", but it's not clear how easy it will be to compose implementations out of partial, default implementations.
    transients - Nice idea for speeding up single-threaded use of persistent data structures. Transients don't respond to all the same interfaces as their persistent counterparts, though, limiting their usefulness. Transients are already being rethought and are likely to be reworked into something new." "

" Dense functions Clojure makes it really easy to create quite dense functions. I sometimes find myself combining five or six data structure manipulation functions in one go, then taking a step back and look at the whole thing. It usually makes sense the first time, but coming back to it later, or trying to explain what it does to a pair is usually quite complicated. Clojure has extraordinarily powerful functions for manipulation of data structures, and that makes it very easy to just chain them together into one big mess. So in order to be nice to my team mates (and myself) I force myself to break up those functions into smaller pieces. Naming One aspect of breaking up functions like described above, is that the operations involved are usually highly abstract and sometimes not very coupled to domain language. I find naming of those kind of functions very hard, and many times spend a long time and still not coming up with something I’m completely comfortable with. I don’t really have a solution to this problem right now. Concurrency For some reason, we haven’t used most of the concurrency aspects of Clojure at all. Maybe this is because our problems doesn’t suit themselves to concurrent processing, but I’m not sure this is the root of the reason. Suffice to say, most of our app is currently quite sequential. We will see if that changes going forward. Summary I’ve been having a blast with Clojure. It’s clearly the exactly right technology for what I’m currently doing, and it’s got a lot of features that makes it very convenient to use. I’m really looking forward being able to use it more going forward. "

https://olabini.com/blog/2012/08/6-months-with-clojure/

http://www.winestockwebdesign.com/Essays/Lisp_Curse.html https://news.ycombinator.com/item?id=2450973

" Clojure favours discrete components that do one particular job. For instance, protocols were introduced to Clojure to provide efficient polymorphism, and they do not attempt to do anything more than this.

Ruby is an object orientated language, and tends to favour grouping together a wide range of functionality into indivisible components. For instance, classes provide polymorphism, inheritance, data hiding, variable scoping and so forth.

The advantage of the Clojure approach is that it tends to be more flexible. For instance, in Sinatra you can write:

  get "/:name" do |name|
    "Hello #{name}"
  end

And in Compojure, you can write:

  (GET "/:name" [name]
    (str "Hello " name))

Superficially they look very similar, but their implementation is very different. The Sinatra code adds the enclosed block to a hash map in the current class, whilst the Compojure code just returns an anonymous function. The Clojure approach sacrifices some convenience for greater flexibility. For instance, in Compojure I can write:

  (context "/:name" [name]
    (GET "/email" []
      (str name "@example.com"))
    (GET "/greet" []
      (str "Hello " name)))

Because each route is discrete and independent of any larger construct, I can easily take routes and use other functions and macros to group them together.

I may be wrong, but I don't think there's an easy way of doing this in Sinatra, because routes are bound to a class when they are created. "

shaunxcode 1563 days ago

link

I am sure this has been discussed to death somewhere else (point me there?) but what is the advantage of adding [ ] for defining arguments to a proc/lambda? I assume it is to help it stand out a little more? What I love about lisp/scheme is the symmetry and simplicity in syntax - though it does clearly state in the scheme spec that [ ] are reserved for future language revisions.


jefffoster 1563 days ago

link

Clojure extends the syntax of Lisp to include first class support for data structures other than lists (vectors, sets and maps).

Vectors are the [ ] structure you see. You can use ( ) to specify a number of function bodies which differ on arity e.g:

  (defn foo ([x] x) ([+ x y])

So it's not to stand out more, rather it's extending the Lisp syntax to make items other than lists "first-class" whilst retaining the homoiconic properties of Lisp.

---

http://groups.google.com/group/clojure/browse_thread/thread/3a76a052b419d4d1/d57ae6ad6efb0d4e?#d57ae6ad6efb0d4e

http://groups.google.com/group/clojure/browse_thread/thread/8b2c8dc96b39ddd7/5237b9d3ab300df8

--- " Clojure-contrib also prompted a question that every open-source software project must grapple with: how to handle ownership. We'd already gone through two licenses: the Common Public License and its successor, the Eclipse Public License.

Rich proposed a Clojure Contributor Agreement as a means to protect Clojure's future. The motivation for the CA was to make sure Clojure would always be open-source but never trapped by a particular license. The Clojure CA is a covenant between the contributor and Rich Hickey: the contributor assigns joint ownership of his contributions to Rich. In return, Rich promises that Clojure will always be available under an open-source license approved by the FSF or the OSI.

Some open-source projects got stuck with the first license under which contributions were made. Under the CA, if the license ever needs to change again, there would be no obstacles and no need to get permission from every past contributor. Agreements like this have become standard practice for owners of large open-source projects like Eclipse, Apache, and Oracle. "

---

" The growth of contrib eventually led to the need for some kind of library loading scheme more expressive than load-file. I wrote a primitive require function that took a file name argument and loaded it from the classpath. Steve Gilardi modified require to take a namespace symbol instead of a file. I suggested use as the shortcut for the common case of require followed by refer. This all happened fairly quickly, without a lot of consideration or planning, culminating in the ns macro. The peculiarities of the ns macro grew directly out of this work, so you can blame us for that. "


" Clojure 1.3, the first release to break backwards-compatibility in noticeable ways (non-dynamic Vars as a default, long/double as default numeric types).

"

---

" To take one prominent example, named arguments were discussed as far back as January 2008. Community members developed the defnk macro to facilitate writing functions with named arguments, and lobbied to add it to Clojure. Finally, in March 2010, Rich made a one-line commit adding support for map destructuring from sequential collections. This gave the benefit of keyword-style parameters everywhere destructuring is supported, including function arguments. By waiting, and thinking, we got something better than defnk. If defnk had been accepted earlier, we might have been stuck with an inferior implementation. "

--- "

It's a difficult position to be in. Most of Clojure/core's members come from the free-wheeling, fast-paced open-source world of Ruby on Rails. We really don't enjoy saying "no" all the time. But a conservative attitude toward new features is exactly the reason Clojure is so stable. Patches don't get into the language until they have been reviewed by at least three people, one of them Rich Hickey. New libraries don't get added to clojure-contrib without multiple mailing-list discussions. None of the new contrib libraries has reached the 1.0.0 milestone, and probably won't for some time. These hurdles are not arbitrary; they are an attempt to guarantee that new additions to Clojure reflect the same consideration and careful design that Rich invested in the original implementation. "

" With the expansion of contrib, we've given name to another layer of organization: Clojure/dev. Clojure/dev is the set of all people who have signed the Clojure Contributor Agreement. This entitles them to participate in discussions on the clojure-dev mailing list, submit patches on JIRA, and become committers on contrib libraries. Within Clojure/dev is the smaller set of people who have been tasked with screening Clojure language tickets. Clojure/core overlaps with both groups. "

"

At the tail end of this year's Clojure/conj, Stuart Halloway opened the first face-to-face meeting of Clojure/dev with these words: "This is the Clojure/dev meeting. It's a meeting of volunteers talking about how they're going to spend their free time. The only thing we owe each other is honest communication about when we're planning to do something and when we're not. There is no obligation for anybody in this room to build anything for anybody else." "

---

clojure does named arguments via destructuring:

http://stackoverflow.com/questions/3337888/clojure-named-arguments

however, imo this is a little verbose:

(defn blah [& {:keys [key1 key2 key3]}] (str key1 key2 key3))

you have the cognitive effort required to parse an extra two levels of nesting (i mean the { and the inmost [), and the typing effort required to type it, as well as & and :keys

and default arguments are even more verbose:

(defn blah [& {:keys [key1 key2 key3] :or {key3 10}}] (str key1 key2 key3))

python's

def blah(key1=None,key2=None,key3=10): return '%s%s%s' % (key1, key2, key3)

seems easier to read and to type (and would be more concise, if there were not keyword args without defaults in the example, or if python had a variadic 'str')

---

---

in any case, good idea to include all of clojure's destructuring stuff, such as:

clojure's & destructuring:

(defn blah [& [one two & more]] (str one two "and the rest: " more))

  1. 'user/blah user> (blah 1 2 "ressssssst") "12and the rest: (\"ressssssst\")"

and map destructuring:

user> (defn blah [& {:keys [key1 key2 key3]}] (str key1 key2 key3))

  1. 'user/blah user> (blah :key1 "Hai" :key2 " there" :key3 10) "Hai there10"

---

very quick Clojure interop tutorial:

http://augustl.com/blog/2013/zeromq_instead_of_http/

" ;; Quick Clojure tutorial, with Java equivalents

 ;;  Java method call on string instance. Returns "A STRING"(.toUpperCase "a string") "a string".toUpperCase()
 ;; Java method call with one argument. Returns byte[].(.getBytes "a string" "UTF8") "a string".getBytes("UTF8")
 ;; Calls the constructor and creates a new instance of Point(Point. 15 21) new Point(15, 21)

;; Create a new thread and start it (.start (Thread. (fn [] ...))) Thread t = new Thread(aClojureFunctionHere) t.start() "

--

toread the talk that https://news.ycombinator.com/item?id=3135185 is about

toread http://channel9.msdn.com/shows/Going+Deep/Expert-to-Expert-Rich-Hickey-and-Brian-Beckman-Inside-Clojure/

toread https://github.com/jennifersmith/clojure_west_notes/blob/master/why_clojure_sucks.org

toread++: https://speakerdeck.com/stilkov/clojure-for-oop-folks-how-to-design-clojure-programs

toread http://www.slideshare.net/yoavrubin/oop-clojure-style-long

http://stackoverflow.com/questions/1548209/is-clojure-object-oriented-at-its-heart-polymorphism-in-seqs

---

some clojure features from https://speakerd.s3.amazonaws.com/presentations/2471a370b3610130440476a0f7eede16/2013-05-17-ClojureOOP-Geecon.pdf :

ersistent data structures ‣ Sequences ‣ Support for concurrent programming ‣ Destructuring ‣ List comprehensions ‣ Metadata ‣ Optiional type information ‣ Multimethods ‣ Pre & Post Conditions ‣ Records/Protocols ‣ Extensive core and contrib libraries

--

" To take one prominent example, named arguments were discussed as far back as January 2008. Community members developed the defnk macro to facilitate writing functions with named arguments, and lobbied to add it to Clojure. Finally, in March 2010, Rich made a one-line commit adding support for map destructuring from sequential collections. This gave the benefit of keyword-style parameters everywhere destructuring is supported, including function arguments. By waiting, and thinking, we got something better than defnk. If defnk had been accepted earlier, we might have been stuck with an inferior implementation. " discussed: http://groups.google.com/group/clojure/browse_thread/thread/aa57ab265f7474a/51bb53ca077154f8 defnk: http://groups.google.com/group/clojure/browse_thread/thread/de791a1a28659ea/6020c7db6bb74844 lobbied: http://groups.google.com/group/clojure/browse_thread/thread/de791a1a28659ea/6020c7db6bb74844 one-line commit: https://github.com/clojure/clojure/commit/29389970bcd41998359681d9a4a20ee391a1e07c

bayle: however imo this wasn't a great solution b/c it requires non-concise destructuring of the 'rest' (variadic) formal parameter: http://stackoverflow.com/questions/3337888/clojure-named-arguments

--

refer, require, use, ns, import-static

--

" Clojure is a practical language that recognizes the occasional need to maintain a persistent reference to a changing value and provides 4 distinct mechanisms for doing so in a controlled manner - Vars, Refs, Agents and Atoms. "

---

a discussion of scoping and clojure that mentions that vars used to have dynamic scope before they were made thread-global:

http://stuartsierra.com/2013/03/29/perils-of-dynamic-scope

---

bayle: my observation: clojure hates OOP encapsulation! witness:

--

Clojure avoids most of these issues by:

    Making all variables and data structures immutable by default. If you look at the "cheat sheat" summary above, this alone eliminates a huge range of concurrency problems that one has to deal with in Java (and other languages that provide mutable variables and data structures as the default).
    Using Agents to manage the update of independent values in their own thread of execution. Agents run in a thread pool and have a consistent execution sequence. Therefore (as explained in the documentation), "The actions of all Agents get interleaved amongst threads in a thread pool. At any point in time, at most one action for each Agent is being executed. Actions dispatched to an agent from another single agent or thread will occur in the order they were sent, potentially interleaved with actions dispatched to the same agent from other sources."
    Making provision for thread-local bindings of "inherited" variables (called "Vars" in Clojure). By default, threads will inherit the value of any Vars that have been set by the calling process. However, threads can create their own "local binding" to these Vars without affecting the value of the Var in the calling process or in another thread. The "local binding" can be established temporarily in a thread with the "root" binding being restored afterwards. All such temporary bindings are local to the thread only.
    Providing Software transactional memory (STM) control of updates to thread-shared variables (called "Refs" in Clojure). Multiple agents may all need to update the same Ref. Clojure's STM implementation provides "a"tomicity (every change to Refs made within a transaction occurs or none do), "c"onsistency (Refs remain in a consistent state before the start of a transaction and after the transaction is over regardless of whether the trasaction was successful or not), and "i"solated (no transaction sees the effects of any other transaction while it is running) ACID properties for any update of a Ref. Clojure's STM uses multiversion concurrency control with adaptive history queues for snapshot isolation, and provides a commute operation. This means that any "reads" of Refs are guaranteed to provide a consistent set of values ("snapshot") regardless of whether there are currently updates occuring to the Refs in other threads and any "writes" of the Refs are guaranteed to be applied against the same snapshot that was read. Clojure's STM manages the necessary locking (behind the scenes) of all the objects. The commute operation allows Clojure programs to read-and-update a Ref within a transaction (for example, if multiple Agents are incrementing a counter, it's important that they all be able to correctly update the counter; however, the updates do not necessarily need to occur in any particular order).

The following simplistic diagram illustrates a very basic example of these Clojure concurrency features. In the diagram, a Clojure program has created 4 separate Agents, 2 Vars, and 1 Ref. All 4 Agents can return their own independent values when queried by the calling process. Agents 1 & 2 both reference the Var 1 that was defined at the "root" level; however, when Agent 2 changes the value of Var 1, that change is local to Agent 2 only. Agent 1 sees the "root" binding of Var 1 and Agent 2 sees the local binding of Var 1 and neither see the alternative bindings. Agents 3 & 4 both need to update the value of Ref 1. However, since Refs can only be updated inside an STM transaction, the "A", "C", and "I" ACID properties of the data are maintained.

--

"

When compared to Java/C++, Clojure's approach to concurrency is much simpler and easier to "get right" when you're coding concurrent programs. However, Clojure isn't the only language to provide "native" support for concurrency. It's interesting to compare the approach that Clojure has taken with the approach that Erlang (another language that has built-in support for concurrency) has taken. I've posted a bit about Erlang in the past on my blog; so, although I'm not an Erlang expert, I do know something about the language. I'll focus on three main areas as a basis for comparison:

    Autonomous/independent threads of execution: Actors in Erlang, Agents in Clojure. Erlang's actors are light-weight "user-land" processes (e.g. - "green threads") that are autonomous and can scale tremendously. Clojure's agents are native threads that are managed in thread pools for performance. As explained in the documentation, "Clojure's Agents are reactive, not autonomous - there is no imperative message loop and no blocking receive". Rich Hickey has summarized the Actor/Agent differences and his design rationale:
        "There are other ways to model identity and state, one of the more popular of which is the message-passing actor model, best exemplified by the quite impressive Erlang. In an actor model, state is encapsulated in an actor (identity) and can only be affected/seen via the passing of messages (values). In an asynchronous system like Erlang's, reading some aspect of an actor's state requires sending a request message, waiting for a response, and the actor sending a response. It is important to understand that the actor model was designed to address the problems of distributed programs. And the problems of distributed programs are much harder - there are multiple worlds (address spaces), direct observation is not possible, interaction occurs over possibly unreliable channels, etc. The actor model supports transparent distribution. If you write all of your code this way, you are not bound to the actual location of the other actors, allowing a system to be spread over multiple processes/machines without changing the code.
        I chose not to use the Erlang-style actor model for same-process state management in Clojure for several reasons:
            It is a much more complex programming model, requiring 2-message conversations for the simplest data reads, and forcing the use of blocking message receives, which introduce the potential for deadlock. Programming for the failure modes of distribution means utilizing timeouts etc. It causes a bifurcation of the program protocols, some of which are represented by functions and others by the values of messages.
            It doesn't let you fully leverage the efficiencies of being in the same process. It is quite possible to efficiently directly share a large immutable data structure between threads, but the actor model forces intervening conversations and, potentially, copying. Reads and writes get serialized and block each other, etc.
            It reduces your flexibility in modeling - this is a world in which everyone sits in a windowless room and communicates only by mail. Programs are decomposed as piles of blocking switch statements. You can only handle messages you anticipated receiving. Coordinating activities involving multiple actors is very difficult. You can't observe anything without its cooperation/coordination - making ad-hoc reporting or analysis impossible, instead forcing every actor to participate in each protocol.
            It is often the case that taking something that works well locally and transparently distributing it doesn't work out - the conversation granularity is too chatty or the message payloads are too large or the failure modes change the optimal work partitioning, i.e. transparent distribution isn't transparent and the code has to change anyway.
        Clojure may eventually support the actor model for distributed programming, paying the price only when distribution is required, but I think it is quite cumbersome for same-process programming. YMMV of course."
    Safe use/update of shared data: Mnesia in Erlang, STM in Clojure. In Erlang, control of updates to thread-shared data is handled by Mnesia, a distributed DBMS. This provides full ACID data integrity; however, Erlang is designed to be a distributed concurrency language and Clojure is not, so the cost/benefit of using a DBMS for non-distributed data integrity has to be taken into consideration. Clojure's STM-based approach is an effective, well-performing solution that doesn't require any out-of-process storage. Although STM has recently had some vocal detractors, most of the criticisms of STM would appear to apply more in an environment where mutable variables/structures are the norm. There is a thread on the Clojure mailing list that details a number of reasons why Clojure's use of STM avoids many of the things that STM has been criticized for. As concurrency-guru Cliff Click says in the thread:
        "I think Clojure is on to something good here - Immutability IS the Big Hammer For Concurrency here, and the STM is just one of several flavors of 'Big Hammer isn't working so I need another approach'. Given the Immutability-By-Default, STM has a much better chance of being performant than in other languages so it makes sense to give it a go."
    Distributed concurrency: This one really subsumes the other two (as well as some others that I won't be discussing here). Providing native support for distributed concurrency (also called parallel computing) affects quite a few design choices when you're building a language. Erlang was designed from the outset to support both distributed and non-distributed (same-address-space) concurrency. Therefore, Erlang uses the same actor-based messaging model in both distributed and non-distributed processing environments and Mnesia is designed to provide a shared data store for Erlang processes that are not necessarily running on the same machine. These choices make sense for Erlang because Erlang has a unified approach to both distributed and non-distributed concurrency. Clojure deliberately does not have any unified, "built-in" support for distributed concurrency. This decision means that distributed concurrency can be more difficult to program in Clojure than in Erlang; however, it also means that non-distributed concurrency (which, for many applications, will be the more common concurrency scenario) can be catered for in an easier manner. In a comparison of the two languages, Rich Hickey explained his reasons for not designing Clojure with support for a distributed concurrency model:
        "I have some reservations about unifying the distributed and non-distributed models (see e.g. http://research.sun.com/techrep/1994/smli_tr-94-29.pdf), and have decided not to do so in Clojure, but I think Erlang, in doing so, does the right thing in forcing programmers to work as if the processes are distributed even when they are not, in order to allow the possibility of transparent distribution later, e.g. in the failure modes, the messaging system etc. However, issues related to latency, bandwidth, timeouts, chattiness, and costs of certain data structures etc remain. My experiences with transparent distribution were with COM and DCOM, and, quite frankly, not happy ones. I think Erlang has a much better story there, but the fact is that distributed programming is more complex, and I personally wouldn't want to incur that complexity all the time. I think it is ok for a system designer to decide one part of a system will be distributed and another not, and incur some rewrite if they are wrong. If I wrote phone switches I might think otherwise :) "

---

here's how 'trampoline' works in clojure:

http://pramode.net/clojure/2010/05/08/clojure-trampoline/

---

https://docs.google.com/presentation/d/15-7qFy6URdE7Owi2LitkQI_OHBu1AFWPUwHxgBc-O4E/edit#slide=id.g17917bc33_00

" Static types. (core.typed) Unit testing. (clojure.test) Declarative programming. (core.logic) Lightweight threads. (core.async) To run in the browser. (ClojureScript?) High performance. (JVM interop) "

--

https://docs.google.com/presentation/d/15-7qFy6URdE7Owi2LitkQI_OHBu1AFWPUwHxgBc-O4E/edit#slide=id.g17917bc33_00

java/clojure

j:

1 + 2 5 + 6 * 7 + 8 * 9

0 <= x && x <= 1

c:

(+ 1 2) (+ 5 (* 6 7) (* 8 9))

(and (<= 0 x) (<= x 1)) or:

(<= 0 x 1)

j:

“cats”.length()

s.equals(t)

Math.pow(2.718, 3.14)

new Point(5, 6)

c:

(.length “cats”)

(.equals s t) or: (= s t)

(Math/pow 2.718 3.14)

(new Point 5 6) or: (Point. 5 6)

j:

class Point { … }

interface Foo { … }

public static void main(String[] args)

c:

(defrecord Point [x y]) ;; .hashCode and .equals ;; already defined!

(defprotocol Foo … )

(defn -main [args] …)

j:

System.out.println(“verbose”)

new int[1024]

a[0] a[1] = 2

c:

(println “not verbose”)

(int-array 1024)

(aget a 0) (aset a 1 2)

j:

if (x < 0) { S.o.p(“negative”) } else { S.o.p(“positive”); }

(x < 0) ? “+” : “-”

c:

(if (< x 0) (println “negative”) (println “positive”) )

(if (< x 0) “+” “-”)

j:

(if (< x 0) (println “negative”) (println “positive”) )

(if (< x 0) “+” “-”)

c:

(dotimes [i 100] (foo i)), or:

(doseq [i (range 100)] (foo i))

(while (foo) (bar))

Clojure brings a lot more to the game (1)...

(fn [x] (+ x 3)) or #(+ % 3) ;; anonymous functions

(map #(* % 11) ‘(1 2 3)), or: (for [i ‘(1 2 3)] (* i 11)) ;; evaluates to (11 22 33)

(eval (list ‘+ 5 6)) ;; all code is data ;; => (eval ‘(+ 5 6)) => (+ 5 6) => 11

--

" Clojure has transients, which basically means "inside this function, use a mutable value under the hood while building up the value, but make it immutable before it's returned". " -- http://augustl.com/blog/2014/an_immutable_operating_system/

--

jamii 1 day ago

link

I spent a total of about 11 months (spread over a few years) consulting on a betting exchange in erlang. It was pretty painful even with lots of erlang experience.

I sat down one week and prototyped a rewrite of the core exchange in clojure. 40x better throughput and 10x better latency despite using only two threads, naive `pr-str` serialisation and storing all the exchange state in one huge immutable data structure (for easy rollback on errors). Much easier to debug and to wire up different setups without modifying code (eg swapping out the network layer without touching the io and batching code).

There are some interesting ideas in erlang but they are hidden behind an inexpressive language and poor libraries. I would think more than twice before taking on another large erlang project.

reply

paperwork 1 day ago

link

As non-mainstream languages go, in the financial industry, Scala is picking up steam. However, clojure is at the top of my list as the language I want to learn, despite obstacles of limited time. Lisp has an impressive pedigree and Rich Hickey is a smart, pragmatic dude who seems to have spent a great deal of time thinking about reducing complexity in real-world software engineering.

reply

jamii 1 day ago

link

I think that's the real strength of clojure - not the language itself but that the community around it is focused on radically reducing complexity.

Every project I have ever worked on that struggled or failed did so because the complexity outgrew the developers ability to manage it. It kind of scares me that scala is growing so quickly - from my experience working on a large scala project it seems to breed complexity like nothing else I've ever worked with (haskell, python, ocaml, erlang, clojure).

reply