Clojure vs. Scala
programming-puzzler.blogspot.se
programming-puzzler.blogspot.se
Suits would surely be case objects rather than classes, or I'd be tempted to just use a Java enum (Scala could really do with an equivalent). With card ranks, either you want to be able to compare them - in which case integers are the correct representation - or they're just 13 "things", in which case again an enum-like representation is good; I can't imagine why you'd ever want to represent a card rank as int-or-string.
Case classes are java-serializable by default. Any number of other serializers (json, database...) exist that work seamlessly with case classes. I do think it would be nice to have an abstraction for something weaker than a class, something that was guaranteed to be inert data, but still typed.
And yeah, I might implement a deck as something that contains a sequence. But I could then derive typeclasses for sequence operations quite cheaply using lenses. (I do wish there was a nicer way of doing this kind of delegation though).
Sure, Scala steers you towards making a type straightjacket for yourself; you definitely can operate on stringly typed or maply typed data, as it sounds like Clojure encourages you to do all the time. And maybe it's not the best language for exploratory manipulation of semi-structured data. But when the time comes to write a production-ready system, the strong types that Scala gives you are a much faster way to achieve the level of reliability you need than the level of testing you need in a less typed language.
(And even if you don't need the safety, I find types are a great aid to figuring out WTF you were doing if you come back to the code in six months' time)
I have serious doubts about this since practically everything in the Scala standard library is horribly broken (collections, views, everything in scala.io, scala.xml and scala.actors etc.). The number of bugs found by Paul Phillips alone is absolutely ludicrous.
As it stands now, both Haskell and Clojure have vastly superior libraries, build systems that actually work, and implementations that people actually understand and can modify.
Paul has definitely found a lot of "bugs", he has certainly contributed a huge amount to the cleanliness of the code base, and has been a big pusher for removing certain levels of unsoundness in the type system, but if you were looking for bug-finders + fixers, Simon Ochsenreither is probably a better bet.
Foldable, Traversable, Monoid etc. are all far better abstractions than what Scala collections provide, and they don't lead to unmanageable inheritance hierarchies and ridiculous APIs. How many bugs have been caused by that? A few hundred maybe? Does Set equality work in the current release or is that broken again? Is it possible to do thread safe efficient merges in immutable collections yet?
The only thing Scala collections offers that Haskell doesn't is the CanBuildFrom travesty. At least in Clojure you can chain transformations together without generating tons of intermediate values. In Scala you're forced to choose between being ridiculously inefficient or using iterators.
(It seems like you've got interesting things to say, but you're being a bit strident. It bums me out that Scala discussions on HN take such a heated tone.)
In the rare cases where you actually need to convert between collection types, it's usually trivial and more efficient to do it manually. Essentially they've added tons of complexity for negligible benefit. One of my main gripes with the Scala collections library is that it's overly complex and that this complexity has been the source of hundreds of bugs.
If there was any real benefit to the way it's currently done they wouldn't be hiding the true type signature for things like `List.map` in the official Scaladocs.
Additionally, it seems that moving from lists to a different collection type still involves rewriting your whole code due to the insane idea of using different functions by default (fmap/map, mempty/[], mappend/++).
I just don't have to deal with any of these design mistakes in Scala. Having sane defaults and stuff that "just works" consistently are a huge benefit.
I can't even imagine what you are talking about, so I can't say if they "fixed it" or not.
>Additionally, it seems that moving from lists to a different collection type still involves rewriting your whole code due to the insane idea of using different functions by default
"I did something dumb and now I don't like it". So, don't do it? Why were you using map in the first place?
>I just don't have to deal with any of these design mistakes in Scala
I don't have to deal with them in haskell either. But I do have to deal with the hundreds of other mistakes scala made if I use it.
Maybe I'm feeding a troll here, but I've been building production software for over 4 years using SBT. It works.
> Akka actors (which are awesome, BTW).
PartialFunction[Any, Unit]. Ridiculous.
That's bad?
As for the PartialFunction[Any, Unit] comment: well, yeah. That's the way actors work. They can be sent any message but may or may not respond. The company I work for has a very large Akka-based backend and that has honestly never been an issue for us. Moreover, it's necessary to facilitate hop-swapping (changing an actor's behavior dynamically during runtime). That is most certainly not a feature I would be willing to sacrifice. "context.become" is an incredibly powerful tool within Akka, especially in how it makes modeling a finite-state machine quite trivial in practice.
We use the FSM helper [1] heavily in our backend at Conspire. For data-pipeline use cases, it's invaluable. Keeps our code concise, readable and testable. Even basic become/unbecome operations are big for us, I've written up one of their use cases on our blog [2].
[1] http://doc.akka.io/docs/akka/2.2.3/scala/fsm.html
[2] http://blog.goconspire.com/post/64901258135/akka-at-conspire...
I think this illustrates the author's point that languages structure our thinking. The value of a King as 13 and the Queen as 12 is determined by the rules of the card game, not the number of elements displayed on its face as is the case for the non-face cards - e.g. the face cards all have a value of 10 in blackjack.
Some languages better allow for the messy details of the world and allow the programmer to treat the partial and occasional isomorphism between names of and the values assigned to cards in a particular context uncomplected.
None of which is to say that strong static typing does not offer a particular set of advantages, only to suggest that it can become forced at a certain level of abstraction where real world objects are represented without a specific context for interpretation.
Imagine a program which begins by asking, "Which game do you want to play?" and includes a card guessing game, blackjack, Uno!, go fish, and crazy eights. We want the values of the cards to lie in their use within each game, not where we shuffle and deal and discard from hands.
I maintain that you gain nothing (except the possibility of errors) by representing the cards as int-or-string here. Think about how you'd actually use the cards in implementing those games. With my enum-like scala representation you could do anything you could do with the clojure version, and you'd get the advantage of e.g. compiler warnings if you defined incomplete match/case statements.
And this is the really important point, this pushing the abstraction down to a lower level causes it to leak into the implementation of card games in ways that make the card games themselves harder to reason about.
Decks of cards have Kings, not 13's, and using 13 to represent the King is of type leaky abstraction. It's a muddy road leading into the swamp.
When I implement blackjack I get to write little balls of mud:
If card = 13 then 10
And in my card guessing game If card = 13 and guess = King then "Yes, the card is a King!"
All that for a warning when I don't have a card 7, which is going to make Pinochle have to work around the abstraction - probably by bypassing the entire implementation when it chokes on two Jacks of Diamonds etc. in a deck.There's no value to a warning that's irrelevant and perhaps even negative value if it encourages the idea of adding a wildcard last case just so I don't have it interrupting my thoughts each and every time I build my program since doing so opens the door to very run time errors it was supposed to prevent or conversely creates the habit of ignoring the compiler warnings in the very worst situation - when I think I know what I am doing.
If enums were good enough and I wanted to ignore compiler warnings, I could have used C.
What? No it doesn't. Scala case objects don't have numerical values.
object Cards {
sealed trait Suit
case object Spades extends Suit
case object Clubs extends Suit
case object Hearts extends Suit
case object Diamonds extends Suit
sealed trait Rank
trait FaceCard extends Rank
case object Jack extends FaceCard
case object Queen extends FaceCard
case object King extends FaceCard
case object Ace extends FaceCard
case class ValueCard(n: Int) extends Rank
case class Card(rank: Rank, suit: Suit)
type Deck = IndexedSeq[Card]
def numberToRank(n: Int): Rank = n match {
case 1 => Ace
case x if x <= 10 => ValueCard(x)
case 11 => Jack
case 12 => Queen
case 13 => King
case 14 => Ace
case _ => throw new IllegalArgumentException
}
def numberToSuit(n: Int): Suit = n match {
case 0 => Spades
case 1 => Clubs
case 2 => Hearts
case 3 => Diamonds
}
def apply(): Deck = for(rank <- 1 to 13; suit <- 0 to 3) yield Card(numberToRank(rank), numberToSuit(suit))
}
Now I have: normal operations bound to Deck (head, take, drop etc) and the ability to pattern match on cards. Also strongly typed. This took about 5 minutes top to write.edit: I'd say the 'numberToRank' parts are probably code smells, but given that its Christmas Eve, I really can't be bothered to think of a better solution right now. Implementing ordering etc can be done on the traits very easily too, but that is game specific (for example are aces high or low, is a King ranked higher than a Jack etc).
In blackjack:
case 10 => Jack or Queen or King or 10
and case Ace => 1 or 11
and poker with "Dr Pepper" case 10 => ace or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or jack or queen or king
case 2 => ace or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or jack or queen or king
case 4 => ace or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or jack or queen or king
It is sometimes better if the data type provides abstractions over the messy details of the world rather than adding another one to it and planning for the possibility of a Joker in the deck might make sense. object Cards {
sealed trait Suit
case object Spades extends Suit
case object Clubs extends Suit
case object Hearts extends Suit
case object Diamonds extends Suit
sealed trait Rank
trait FaceCard extends Rank
case object Jack extends FaceCard
case object Queen extends FaceCard
case object King extends FaceCard
case object Ace extends FaceCard
case class ValueCard(n: Int) extends Rank
case class Card(rank: Rank, suit: Suit)
type Deck = IndexedSeq[Card]
val faceCardRanks = IndexedSeq(Jack,Queen,King,Ace)
val valueCardRanks = for (rank <- 2 to 10) yield ValueCard(rank)
val ranks = IndexedSeq.concat(valueCardRanks,faceCardRanks)
val suits = IndexedSeq(Spades,Clubs,Diamonds,Hearts)
def apply(): Deck = for(rank <- ranks; suit <- suits) yield Card(rank, suit)
}He's comparing the structure and design of programs between two great languages of similar capabilities (features) and philosophies. I think his point about lightweight data modeling is spot on - Clojure is a language that goes out of its way to make modeling your problems idiomatically painless and dramatically simpler than most other languages.
It's idiomatic in Clojure to use plain old maps to represent structures or entities in your program that you would otherwise represent with a class or equivalent construct in many Object Oriented languages. That isn't a code smell either - it's idiomatic and wonderful. Keywords (similar to Ruby symbols in both use and philosophy) are wonderful things that together with maps make enums as a language primitive irrelevant.
That isn't an argument against any point I've made. Please show me some code and give me some context that will make me rethink my assumptions and arguments if you feel the need to point out my flawed thinking.
Lightweight data modeling is important and Java is truly terrible at this. I illustrated this in a gist about creating and iterating over a list and a map, and contrasting that to the equivalent Python: https://gist.github.com/amontalenti/8114359
The author says about Scala: "Due to its highly detailed static type system, Scala attracts the kind of programmers that like to carefully categorize and enumerate all the possible data structures."
It turns out, this describes expert Java programmers very well, too -- so it's no surprise that Scala is a very popular language with Java programmers. I'm finding that Clojure is the more attractive language if your sensitivities lean toward the "simplicity" and "dynamism" camp. I was reading some Scala code being used in production at Twitter and found this marvel: https://twitter.com/amontalenti/status/410977749629546496 -- you would simply never see anything like this in Clojure or Python codebases.
The point about multi-paradigm is interesting. It's very true that Clojure, unlike Python, does not support true multi-paradigm. Then again, Python does not support "true" functional programming. It's close, but no cigar, due to the lack of full-featured lambdas / blocks. If you have to pick one paradigm, functional is definitely the simpler and more essential one.
Illustration: imagine a variant of Python that forced all code to live in classes -- ick. But imagine a variant of Python without classes -- that's not so bad.
It's worth reading about Clojure's take on object-orientation-atop-functional using multimethods and hierarchies: http://clojure.org/multimethods
(defn f [& args]
(apply g args))
And it usually just takes a couple of strokes in any decent text editor anyway.I'll add one thing to your point - multimethods are a fantastic example of how Clojure allows you to structure programs in functional way that takes a programming feature (in this case polymorphic method dispatch based on the arguments at runtime) to the nth degree.
In Java and many other languages that have polymorphic method dispatch - the dispatch value is usually the type of the object for whom the method is being called on. Functional languages like Haskell take a much more powerful and expressive approach to this and extend it to matching the dispatch values for the called function on the type or pattern matched value of any argument.
Multimethods are essentially Clojure's take on this in a dynamic language. They allow the programmer to define a function that constructs the dispatch value however it wants - usually from the arguments given as input, in any order, on any condition.
To illustrate, I'll give an example I showed my wife yesterday. She's writing a roguelike game in ClojureScript at the moment that you can play in the browser - naturally she needed a way to represent the game world and the players position as they move around.
So, for illustration I'll omit the code for the world state, but she needed to bind event listeners for the keyboard to properly dispatch the proper movement logic. So the code she wrote looked like this:
https://gist.github.com/aamedina/8114944
She came to me with this code with the following problem - how do I think she should organize it? Clearly the handling logic is going to be more complex than a single line per direction (:up, :down, :left, :right) could handle, she has to account for terrain differences, buildings, monsters, what have you.
So I suggested using multimethods to accomplish this. This is what the multimethod solution I cooked up would look like in this case.
https://gist.github.com/aamedina/8114949
Now she has room in those multimethods to handle the potentially complex logic needed to make those arrow keys move the character properly. But another effect of this is that it hides the underlying state mutation from the implementation.
Now your move functions are pure and isolated from the coords atom, in addition to the fact that multimethods made the logic more organized, at the expense of a few more lines of code.
edit: moved code to gist
A typical case where a more complex construct does not help much.
Generic functions were introduced into Lisp, to replace a complex message sending mechanism with a more functional notation.
If the logic would forever stay a one liner, "increment the y coord!" when the up arrow key is pressed, I would agree with you - keep the case statement and go on your merry way. But that isn't the case, and my wife anticipated that being the case, hence the motivation for her question.
Spreading out complex logic into separate functions is usually considered good form. Like every design decision, you have to be wary not to overdo it - but I think my wife was spot on in identifying the need to do so in this case.
Putting the case statement aside for a second - you're also overlooking the fact that by using functions in this way the implementation becomes pure. The functions do not mutate anything and are completely oblivious to the fact that they are being used to swap! out the current position of the player for another.
Some benefits to writing pure functions include easier testing (you don't have to supply an atom to know if your function works, just call the function) and REPL use is vastly simplified. Additionally, I don't even need a browser or a keyboard handler to see if my movement functions work.
First version here: Uses only a case statement to dispatch the proper behavior, but as a pure function.
When she begins to take the state of the map and all of the other variables into account, this case statement will prove confusing to wade through - because it's one function that is doing four related but separate tasks. So version two is what you'd likely end up doing after the code got too unwieldy.
Oh wait... that's what a multimethod is! Now you've gone full circle. I hope you can see the point I was trying to make more generally, beyond the limited example I gave here.
That said, in the example you posted I don't think you are using all the power of the multimethods, because you did not replace the case statement. You went from a case statement to a multimethod+a case statement. In this case, why not let the mutimethod dispatching to do all the routing/case functionality for you? Just use (.-keyCode e) as your dispatch function and use the different values of KeyCodes.XXX for each implementation of the multimethod. And you can even add a :default implementation that leaves the coords untouched.
In my experience the natural use of multimethods arise when you can choose the dispatch function for an element based on either some piece of data from the element or a computed value derived fron the element. But if you have to rely on case/cond/if statements the value of the multimetod is lessened, as you still have to touch your dispatch function when your hierarchy of values change.
edit:typos in the code
Well done, you found some bad code written in Scala. I can assure you I've seen far worse in Python.
(You could also generate exactly this code with macros, which were a new feature in scala 2.10; I imagine a newer version of the code will do that)
Pointing to this kind of stuff as an example of scala shows a complete misunderstanding of the language quite honestly.
val cards = for {
rank <- 'a, '2, [...], 'j, 'q, 'k
suit <- 'h, 'c, 'd, 's
} yield (rank, suit)
val deck = scala.util.Random.shuffle(cards)
which gives you something to play with to validate your model. It's not an api I'd choose to publish, of course, there are many better type-safe ones written here, though I'm sad none of them have used Unicode for suits (I know someone working on a dating app who used a method called ❤ to compare two users: so something like "if (sappho ❤ rhodopis > 95) { ... }")But my point is that Scala gives you the option, and is tolerant, of working in this slightly dirty way, and then lets you clean it up (and a type-safe compiler will flag up where you are using your old api). I don't know clojure well enough, so, what's the similar workflow there?
Scala can solve it in a way quite similar to Clojure. The author is trying to hammer a point about Clojure which doesn't emerge from the example, as most Scala programmers can notice.
It would have been great if he had listed all the possible solutions and told us "Scala lets you solve it in all these very different ways without the language guiding you, Clojure guides you to the simpler solution". That's a fair point, and one Scala programmers will tend to agree with.
The possibility of representing face cards with a name would likely never occur to you, because it would be too complicated to go through the effort of defining the type of a rank to be a "integer or a class comprised of four case classes -- jack,queen,king,ace".
I'm sure every competent Scala developer will see that the equivalent in Clojure can be done with val King = 13; val Queen = 12; ..., which also means you get ordering for free as you're not mixing ints and keywords.I do agree with the author's point that Clojure almost forces you to adopt a simpler design, but I feel that long-term maintainability requires a delicate balance of simplicity and structure that can be achieved with Scala, but takes more self-control with Clojure.
Ordering for free is valuable, I guess, but it sort of depends on the situation. Sometimes face cards are worth 10, other times they are worth 11, 12, 13. If you use val King = 10;, then it suddenly is impossible to distinguish between face cards and tens.
If you didn't want the ordering (or the compiler warning when your blackjack implementation omits queens), you could use Scala's symbols, which are more or less the same as Clojure's keywords:
scala> 'king
res0: Symbol = 'king case class Rank private (val rank : Int) extends AnyVal {
override def toString() = {
rank match {
case 1 => "A"
case 11 => "J"
case 12 => "Q"
case 13 => "K"
case _ => rank.toString()
}
}
}
object Rank {
val Ace = Rank(1)
val Two = Rank(2)
// ...
val King = Rank(13)
} card"Heart:Ace"
or card"$suite:Ace" if you want to reference a variable.However, I think there's a reason Python and Ruby have overtaken Perl in the interpreted language space: Python values simpler solutions [1]. Ruby also values a certain type of beauty over Perl.
Clojure/Scala is a similar dichotomy. Scala has everything you might need, while Clojure is rooted in the spartan power lisp provides.
Also, I wonder: if they weren't both JVM languages, would we even be having this type of discussion? I mean, we aren't comparing Clojure to Cobra or D.
[1] See the Zen of Python http://www.python.org/dev/peps/pep-0020/
Very true. I was thinking of the ethos of Perl and Scala, and wasn't trying to compare the languages directly.
The JVM is key for many as operational stability trumps developer finger-candy. It's an easy sell to add a language change it, when it's not really going to change how an entire part of the organization is going to manage it.
Scala is definitely a TMTOWTDI language.
> However, I think there's a reason Python and Ruby have overtaken Perl in the interpreted language space: Python values simpler solutions [1]. Ruby also values a certain type of beauty over Perl.
Ruby -- like Perl and Scala -- is also a TMTOWTDI language, its probably the most important thing it inherited from Perl (the one Perlism it isn't shedding as it grows.)
So, "Ruby and Python have overtaken Perl" isn't an indicator of something wrong with the TMTOWTDI approach.
type Card = (Any, Any)
type Deck = Seq[Card]
val foo: Deck = Seq((4, 'clubs), ('king, 'diamonds))
It seems to work over here. I would have to be a bit out of my mind to actually want to write code this way, though.As to the "Lightweight data modeling" pitch, there's a pretty large number of other languages that are better suited to this because they have TCO (when run on their main platforms, the analogues of the Sun JVM) and don't require you to debug apparently-endless stack traces in a different language when your completely untyped exploration/prototype inevitably explodes.
A dynamically typed language is obviously easier to begin coding in, especially with a trivial example. The problems that statically typed language solve are usually found in larger code bases and in performance critical applications.
This will be a popular topic for awhile, I believe, because the JVM isn't going anywhere in the next decade (gut feeling).
Clojure programmers choose immutability by default. Scala, however, makes it just as easy to write "var" as it is to write "val", and just as easy to create a mutable collection as it is to create an immutable one.
In my experience, this is far more important for larger code bases than static typing. Luckily, Scala offers immutability as a feature, which is more than can be said about Java/C++/Go/etc.
No, if I was trying to do something intended to be generic, I'd be tempted to start out with this definition of Card:
trait Card
Scala makes it cheap to use descriptive type names while starting out making minimal assumptions. This helps avoid making too many assumptions up front, like the whole model-cards-as-ints one.> For modeling the deck, you probably wouldn't say a Deck is-a sequence, because composition is favored over inheritance.
"X is favored over Y" does not mean "never use Y". And, in Scala, "favor composition over inheritance" mostly strongly applies when you are talking about classes, particular classes as the thing being either composed or inherited from.
I'd probably say something like:
trait Deck[C<:Card] extends Seq[C] {
...whatever special behavior Decks need to support in general...
}That's a feature of Clojure, IMO.
========================
object Cards {
type Deck = Seq[Card]
case class Card(r:Rank, s:Suite)
type Rank = Int
sealed trait Suite
case object Spade extends Suite
case object Heart extends Suite
case object Diamond extends Suite
case object Club extends Suite
}
==================================
Granted, all this code is unnecessary in Clojure but there are two benefits of writing them:
1) compilation type check
2) for developers who have no context of playing cards, this provides a clearer picture, while in the Clojure code they will have to browse more code to get the full picture of the data domain.
I'm perfectly comfortable with both approaches, but as things get bigger, I value explicit structure more than convenience. When I'm dealing with implicit structure, I definitely notice the increased cognitive load.
That's fine for something small, short-lived, and personal. But if I'm doing something large, long-lived, and shared across many people, I think implicit structure gets more and more dangerous. It's hard to keep everybody's mental models aligned over time, which makes it easy to end up with a code base whose coherency declines.
> Sure, you could eschew objects in Scala and mimic Clojure by using generic maps/vectors/lists/sets for all your structured data needs, but that's clearly not how Scala is meant to be used.
Scala isn't "meant to be used" any way. Scala is a lots of things (often times to its detriment) but opinionated is not one. The author even alludes to this early in the post:
> Ten years ago, I would have said that my ideal dream language is one that provides the flexibility to program in any style. I want to be able to choose object-oriented or functional, immutable or mutable, high-level abstractions or low-level speed. With respect to this ideal, Scala clearly wins, supporting more programming styles than Clojure.
Classic fanboyism, as the author purposely created a shitty Scala example. I would advise that someone investigating functional languages on the JVM take this post with a grain of salt.
The author clearly contradicted themself. Did you read the article before downvoting? Also why invoke a false duality like someone being part of the Scala or Clojure community is mutually exclusive? For what its worth, I have used and am a fan of both languages. That won't stop me from pointing out a flawed post though.
Then imagine that you could represent your code as JSON and also that a database existed that let you store and build queries on JSON directly (i.e., Datomic).
I'm not "coming back" to you, as I don't appreciate your snarky, know-it-all tone. I'm replying for the benefit of others reading who may not be familiar with Clojure.
[1] http://clojure.org/datatypes
Any sufficiently complex Lisp implementation contains
an ad hoc, informally-specified, bug-ridden, slow
implementation of half of Haskell's type system.Immutability and programming with data structure literals are what Clojure's all about. Lisp, dynamic, and functional are obviously the other big choices that were made to achieve the overarching goal of the language - simplification. But imo, programming with performant, immutable persistent data structures is the essence of Clojure programming.
Let's not confuse the lack of significant STM transactions with refs and its irk with representing the totality of Clojure's concurrency story. Many Clojure libraries make use of atoms, agents, and dynamic scope (which is arguably a concurrency primitive, given the thread locality of the bindings, but not unique to Clojure).
While "concurrency is not parallelism", parallelism is a special case of concurrent programs that is often challenging. Clojure's offerings here too are compelling - we have the amazing Reducers library which allows you to write higher order functions for collections that, as Rich Hickey put it, "know how to reduce themselves" - and get parallelism (without locks, without even thinking about it really) on top of that.
And then there's the lazy parallel functions - pmap, pcalls, pvalues. Check them out if you haven't. I use Clojure often in a machine learning context with Hadoop/Storm, these abstractions are highly valuable in crafting solutions.
Futures/promises are also widely used in Clojure.
I'm confused as to why you think data structure literals are part of Clojure's core thesis. Perhaps I just don't understand what mean - are you saying that the fact that the Lisp reader can read strings as data structures as being part of what Clojure is about? If so, that statement would apply to all Lisps, not just Clojure.
I use refs, atoms, and agents very sparingly. You just don't need them very much when you have a suite of performant, immutable data structures (with literals). These data structures, and the assortment of polymorphic functions provided in the language to operate on them, imo are the crux of the language. They dominate the experience of the programmer. As far as managing state goes, they make it so that you rarely have to reach for the explicit constructs listed above.
Note that pron said "its emphasis on managing state, especially in the face of concurrency", not "its emphasis on managing state in the face of concurrency". That is, concurrency is an important area where Clojure's state management really shines, but certainly not the only time that managing state is important. I would say managing state is a key aspect of any complex program, even in the absence of concurrency.
I think this actually fits nicely with TFA's assertion about "lightweight data modeling" because managing state and data modelling go hand in hand IMHO. The fact that its lightweight and easy to do (data structure literals also help keep it easy) means that programmers will tend towards this in their solutions.
Actually, I believe Rich Hickey says in his talks that its all about managing complexity and making solutions simple through a datacentric approach. I believe that this is, ultimately, what managing state and lightweight data modelling (and by extension Clojure) is all about and its this philosophy that draws me to Clojure.
I think Mark's right about "lightweight data modeling." It's central to Clojure programming.
Here's an example from Land of Lisp:
(defun limit-tree-depth (tree depth)
(list (car tree)
(cadr tree)
(if (zerop depth)
(lazy-nil)
(lazy-mapcar (lambda (move)
(list (car move)
(limit-tree-depth (cadr move) (1- depth))))
(caddr tree)))))
and here's the version that I re-wrote, using your friend and mine, defstruct: (defun limit-tree-depth (tree depth)
(labels ((limit-depth-move (move)
(let* ((next-tree (limit-tree-depth (get-tree move) (1- depth))))
(if (passing-move-p move)
(make-passing-move :tree next-tree)
(make-attacking-move :src (attacking-move-src move)
:dst (attacking-move-dst move)
:tree next-tree)))))
(make-tree-node :player (tree-node-player tree)
:board (tree-node-board tree)
:moves (if (zerop depth)
(lazy-nil)
(lazy-mapcar #'limit-depth-move
(tree-node-moves tree))))))
I submit that the second may be preferable, even if it is a bit more verbose.Scala doesn't provide the syntactic macros, or the same simpler approach to concurrency.
[1] http://scalamacros.org/paperstalks/2013-09-19-PhilosophyOfSc...
[2] http://scalamacros.org/paperstalks/2013-12-02-WhatAreMacrosG...
[3] http://docs.scala-lang.org/overviews/macros/annotations.html
Clojure - concurrency (immutable by default), macros (lisp-style)
Scala - object/functional fusion, lazy evaluation
Ruby dialect (JRuby or Groovy) - "methodMissing" meta-object protocol
Java - low-level OOP (but functional lambdas and lazy streams coming in Java 8)
COMING SOON: Javascript (Rhino or Nashorn) - functional with prototype-style OOP
I don't think Rhino's being used much in production (correct me if I'm wrong) perhaps because of slow execute times, but the far racier Nashorn is likely to kick out any lingering use of languages used for JVM scripty stuff, e.g. Groovy, Xtend, Beanshell.
My experience so far is Logo when I was 6 and reading Godel, Escher, Bach, but I was thinking yesterday of investigating Clojure and that's my task for today. I'll check back.
It's fairly easy to put another syntax atop Clojure if you want, though in my experience the net effect is to restrict what Clojure can do, beginning with disallowing macros.
If you spend more than a few hours on Clojure (maybe you can create a small toy project) it make much more sense.
I'm experiencing quite the opposite, I'm learning Scala now and it all feels too cumbersome, and even the syntax of Scala bothers me now.
It's great for prototyping but when you start getting big, you really need to tighten these models down to real types.