Why Clojure?
blog.venanti.us
blog.venanti.us
Nice to see a humble post about the language they like without bashing any other language.
It takes quite some time to really kick in (more than one or two pet projects, rather than couple of "hello world"-style lines it took for Ruby), but once it does… walking up and down the ladder of abstraction never felt so good.
(BTW I don't buy the "they're just not used to it" or "they're comfortable with what they know" explanation for this kind of people. These are not stodgy Java programmers who are working in programming as a day-job who are resistant to learning new things, they're people who know more about programming than a hundred average programmers combined and spend nearly every waking minute thinking about how to make it better.)
To further answer your question, "functional programming" isn't always so well-defined. We know, realistically, that pure functional programming isn't going to work for all use cases. Once you're grounded in FP, you think of mutable state (or, in databases, destructive updates and deletes) as an optimization... but sometimes it's an optimization that you need. No language is FP-only because no language can be; even Haskell has the "dirty" IO monad.
I think that most good programmers (like, 99%) recognize the importance of immutability and referential transparency, when possible, and in the function rather than the action being the standard compositional unit for programs. Where there is disagreement is on when, how, and how often to depart from the functional ideal.
unsafePerformIO :: IO a -> a on the other hand does make haskell impure when it is used. And it is used in many libraries.
Hmm, does it really? Is 'unsafePerformIO (return 1)' impure?
Citation needed - or at least clarification as to what "many" means here.
There are lots of smart PL designers working systems languages, and there are lots of smart PL designers working on high-level, functional languages. That doesn't mean that either group is necessarily aware of everything the other group is doing, and it doesn't mean they share the same goals, experience, or taste.
> Martin Fowler
What has this guy done except writing books about "the best way to program" without ever designing a full system himself ?
> Guido van Rossum, Yukihiro Matsumoto
Those guys are just language designers, and the language they designed are just as questionnable as the functional ones, so why do they get a pass actually ? Because their languages are more used ? Do you want to follow suit and say that COBOL creators are probably part of the "world's most experienced programmers" ? What about PHP ?
There are some good points and critiques about the practicality of functional languages, but you don't actually touch on any of them here.
I do think it is interesting though that so many people love to extoll the virtues of immutability and yet have such a hard time putting those virtues into words.
In my own experience, I once built an OO Ruby application that interacted with an internal database to heavily process a CSV file. Dealing with the state of instance variables made debugging maddening. I won't say it had an excellent design, but dealing with shared state made things much worse. If it had been functional and immutable, I wouldn't have had to deal with many bugs I encountered.
Something the author touched on is the use of the REPL, in vim, to attach to remote running processes and debug. Does anybody have a quick example of how to do this?
The start-server command in tools.nrepl https://github.com/clojure/tools.nrepl Will open an nREPL (networked REPL) server on some port of your choosing, by default on localhost. Configure your application to allow this to happen on startup.
SSH from your dev box to the server, and forward the port the REPL is listening on https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding
(having the nREPL port only listen on localhost and then using SSH to access it is somewhat important for security. Someone else who connects to the nREPL port can run arbitrary code with your app's permissions, see any data being processed by your app, etc.)
From a shell:
lein repl :connect <port>
From vim (with tim pope's fireplace plugin):
:Connect nrepl://localhost:<port>
(map (comp (partial g a) f) x)
to be more readable as: (-> g (partial a) (comp f) (map x))https://clojuredocs.org/clojure.core/->
edit: I usually pretty-print it, too:
(-> g ;; input value
(partial a) ;; runs as (partial g a), hands off A
(comp f) ;; runs as (comp A f), hands off B
(map x)) ;; runs as (map B x), this returns a value
or (-> 5
(< 10)) ;; runs as (< 5 10)
=> true
with a little tom-foolery (lambda functions) you can have the incoming value land anywhere in the function's argument listI'm currently quite deep into Javascript/Node, and am already comfortable with functional programming. Next I'm looking to gain more data abilities, and had assumed Python would be the natural next step. You're making me wonder whether Clojure would be a smarter step, though I am concerned that most of the jobs still appear to be in Python.
SimpleTestCase(unittest.TestCase):
def runTest(self):
self.assertEqual(true, true)
can be made more pythonic by using a modern testing framework, py.test: def test():
assert True == TrueAs a non-Clojure developer, having to fight with Clojure configuration files for Riemann was a real pain point, and ended up deciding us against using Riemann for anything as a result (trying to convince a team of sysadmins that they would have to learn Clojure for a single piece of their monitoring infrastructure... didn't work).
IMHO, you're welcome to enjoy your language to the fullest extent possible, I would just ask that you don't force others to use it as well.
EDIT: Since it was asked twice in about 30 seconds, here's why having Clojure as a config option can be bad; it encourages things like this:
https://github.com/guardian/riemann-config/blob/master/main....
Please note that this is considered to be the gold standard by Riemann; it's directly referenced in their documentation.
As for my preferred config file format? ini, yaml, json, in that order. They're simple, there are lots of tools out there to read and write them, and friendly to humans.
It provided powerful abstractions handling streams of monitoring data, but it was too unwieldy to be considered unless you started with Riemann and built out your environment on top of it.
What should it be?
The biggest argument in favor of more friendly config files for me is that they're easier to toss in a jar, though. Nobody wants to fight against configuration in their production environment.
Code is data and data is code. For Lisp programs, configuration is just another Lisp program. I don't want my Lisp programs crippled by a "dumb" config file.
What a config file should be is the simplest, most easy to read and fill out configuration file you can possibly think of. It should be tolerant of simple formatting mistakes and be targeted at someone who is not as smart as you are.
It should also never contain executable code, because config files are rarely as well protected on a system as executable code is, and this could lead to some really nasty vulnerabilities if it's exploited.
The lesson you should learn from this is not to never include powerful statements or interpreted strings in config files, it's that you should test your configs as if they were code, because mistakes in them are just as dire. Even if you limit yourself to key/values in a .properties file, you can still bring down the site if you typo a filesystem path or database name.
[1] http://www.theguardian.com/technology/2009/jan/31/google-bla...
This is true. It does not follow, however, that the concept of code and data is meaningless. Ask anyone who's had to deal with user data unserializing into executable code whether the truth you speak is a blessing or a curse.
> I don't want my Lisp programs crippled by a "dumb" config file. Why should a config file not be "dumb?"
The purpose of a config file is to store state. Why should that be Turing complete? What purpose would it serve?
Ever see an Emacs config file?
I understand the temptation to add evaluation and expressions to everything, but beyond a certain complexity, you no longer have a config file, you're just exposing variables from an application within an application.
As for not using JSON, I prefer YAML b/c often times we like to put comments in our test cases. This is really just no doable in JSON (outside of some ugly hacks. e.g. repeated keys (last one wins)).
I also love immutability because I have had many co-workers who are lazy. Again, not aways the good kind of lazy. I mean the kind of lazy that allows 2 loops with mutable variables into the same function:
howMuchPrizeMoney = 0;
arrayOfMoneyPerCategory =[];
for (i=0; i < users.length; i++) {
u = users[i];
howMuchPrizeMoney += u.prize_money;
}for (i=0; i < contests.length; i++) {
c = contests[i];
for (j=0; j < categories.length; j++) {
cat = categories[i];
if c.name == cat.name {
arrayOfMoneyPerCategory[cat.name] = u.prize_money;
}
}
}Wait, now I have a bug! All the categories have the same amount of money, and I know that is wrong. Where is that bug? Hmm, let me look and look and look. And if this example seems easy, I have seen functions larger than this, where finding the bug gets much harder. I am lazy, and my co-workers are lazy, and if we allow ourselves mutable variables, we will abuse them, so I would be happy if we adopted a language that avoided mutable variables. So for instance, even if I had a loop that looped over all the variables, there would still be no risk of one variable effecting another if I did something like this:
(def vector-of-users [
{:name :henry :prize-money 200}
{:name :henry :prize-money 30}
{:name :craig :prize-money 340}
{:name :craig :prize-money 100}
{:name :pasha :prize-money 2330}
{:name :pasha :prize-money 1130}
{:name :pasha :prize-money 430}
{:name :eli :prize-money 60}
{:name :eli :prize-money 330}
{:name :eli :prize-money 89} ])
(loop [u vector-of-users total 0] (if (first u)
(recur
(rest u)
(+ (:prize-money (first u)) total))
total))
At the REPL, this gives me 5039.Now if I do this, and I stupidly put next a loop inside of a loop, is there any risk of a typo? Sure, but I won't get a confusing number back, instead I'll be told that I'm using a var that doesn't exist.
This is the version without the typo:
(def vector-of-categories [:housing :crisis :restaurants :theater])
(def vector-of-contests [
{:category :housing :prize-money 200}
{:category :housing :prize-money 30}
{:category :housing :prize-money 340}
{:category :crisis :prize-money 100}
{:category :crisis :prize-money 2330}
{:category :restaurants :prize-money 1130}
{:category :restaurants :prize-money 430}
{:category :restaurants :prize-money 60}
{:category :theater :prize-money 330}
{:category :theater :prize-money 89} ])
(loop[money-per-category {}
contests vector-of-contests
categories vector-of-categories]
(if (first categories)
(recur
(assoc money-per-category (first categories)
(loop [c contests total 0]
(if (first c)
(recur
(rest c)
(if (= (:category (first c)) (first categories))
(+ total (:prize-money (first c)))
total))
total)))
contests
(rest categories))
money-per-category))When I try this at the REPL I get:
{:theater 419, :restaurants 1620, :crisis 2430, :housing 570}
But what if I made a typo, just like in the first example? What if instead of this:
(first c)
I stupidly wrote:
(first u)
There would be no "u" that was in scope for the whole of the function. The "u" would only exist inside the first (loop).
Needless to say, no one would ever write Clojure code like this. Anyone who puts 3 loops in function, in Clojure, is taken outside and shot at point blank range. But my first example, in Javascript, is something I have seen in real life.
There are two issues with Clojure (3 if you consider static typing a hard requirement, because core.typed is brilliant but probably not "there" yet) and both come from the JVM. The debugging experience is still pretty bad; although I dislike debugging in general if it can be avoided, and that's why I've gravitated toward static typing (catch bugs early). The second is that the JVM itself imposes a ceiling on how well you can do performance-wise, and can require a fair amount of configuration in production. Of course, Clojure may be off the JVM in 10 years and, even if not, it's still a great language in very many ways.
Leiningen is like an Apple product: it's well-designed and usually 'just works'. But when something goes wrong you realize it's like a black box with only a mystical stack trace to help you.
i mostly make web-apps or data-mining apps, never really understood this criticism of long (1 second type of long, maybe 10 for EE server) startup times for jvm since i can't imagine many apps worth putting a ton of development care into that aren't worth a 1 second wait to run.
i'm not trying to be a smart-ass (i dont believe everything should be blindly forced onto jvm), really just asking why this is such a common concern when in my mind it is an edge-case
For any single long-running application that doesn't spawn more processes, of course it doesn't matter - but most performance issues only matter for some set of applications.
I think it's such a common concern because 1) people often start out writing CLI utils as they learn their way around a language, 2) people would love to see Clojure be better for this use case, and 3) it's one of the few things to complain about.
But I understand on principle, for example I work on JVM & hate when ppl bring in random code that make it hard to test across the whole system. Seamless integration of all tools & unified protocols are nice.
> cat test.lisp
(prin1 "hello, world")
> time clisp test.lisp
"hello, world"
real 0m0.019s
user 0m0.011s
sys 0m0.008s
> cat test.clj
(print "hello, world\n")
> time clojure test.clj
hello, world
real 0m0.929s
user 0m1.283s
sys 0m0.046sCan you elaborate why? Is it also a dependency management tool?