Why Clojure over Python?

Modules can’t be reloaded within the same session. After you imported it once, if you change something in the module, you won’t be able to see the changes reflected.

Agree with your point overall, but I’ve been experimenting with reload (Python 2) and importlib.reload (Python 3) trying to replicate a Clojure-like experience, and I’ve gotten some way. I don’t think it’s as good (I’m not sending expressions to the repl), but I’m managing to do hot reloading within the same repl. Hoping to share the workflow once I can get the code and API into a decent quality.

Can you connect an editor or command-line REPL client directly into a running Python application, possibly running on a remote server, and execute code against it? If you can’t do that, it’s a very different experience to the Clojure REPL, for better or worse.

3 Likes

I can’t imagine needing a team of 20 developers for a Clojure project! I would expect a 20-person Python team could likely be replaced by a much smaller Clojure team and produce the same functionality with a fraction of the amount of code.

On the issue of macros making “Lisp look like whatever language you want” – it’s still going to look like Lisp because that’s the syntax. Macros just turn Lisp code into other Lisp code.

Now if you look at embedded DSLs in Scala, you really can create an alien-looking language that looks nothing like Scala code :slight_smile:

3 Likes

Nope, no editor integration. That’s why I use Clojure when I have the chance!

2 Likes

Thanks for the response Didier.
That really helped me to understand what you value on the Clojure REPL.
I would like to share the information that I have on those observations.

  1. Indeed, Lisp is unbeatable on the S-expr approach. Totally agree.

  2. About dependencies isolation, I beg to differ. Python do have namespaces (aka Modules) that are similar to Clojure Namespaces (both dynamic, both reloadable).Although the are different subtleties, I do not see you claim that Python does not support dependency isolation.

As for virtual-env, it is to isolate cross-project dependencies. They only enhance dependecy isolation, they do not break it.
I do not know how does Clojure handles cross-project dependency isolation.

  1. I think Jupyter Notebooks are the best counter-example for the claim that Python does not have net-based repls. They are exactly that. And because Python was inspired on Lisp, it always had an eval mechanism, making as easy as in clojure to implement a net-based repl.
    Mor info: python 2.7 - How do I embed an IPython Interpreter into an application running in an IPython Qt Console - Stack Overflow

  2. The syntax choices of the languages are indeed different. And I prefer Clojure’s syntax over Python,
    but the whitespaces given the proper IDE are a non-issue the same as parenthesis in Clojure.
    Without a proper IDE or editing environment, I would advocate the Python syntax is more convenient
    because there is no need for manual () matching.

  3. Python have many immutable native datastructures (though not “persistent” as in Clojure). Those exist in Python but as libraries. And mutability is a resource that you can use to various degrees.

  4. Again, syntax. We already agreed that Lisp’s have superior syntax.

  5. Actually, Python modules are object that are created from files (just like in Clojure). So, yes, you can create a module dynamically at the REPL in Python. The semantics of namespaces is a bit different though, but namespaces nonetheless.

  6. Modules can be reloaded. And, like in Clojure, you have to take precautions to be consistent on the consequences of reloading (after all that is why libraries such as mount and Components exist in Clojure world right).

  7. I think you are misusing Python there. The semantics is the similar you create objects (vars) and the can have “symbols” that refer to these objects, and you can even inject those symbols into other modules (namespaces) that are loaded.

  8. Methods can be redefined any time in Python, and they do not require the class to be re-defined. That was misinformation. There are ways to prevent that, but it is allowed by default for custom types.

  9. There is ample support for reflection (and thus introspection) in Python. The thing is that “+” is not a function but a method. And help does work, you just need to give it the method object
    help(1 .__add__). And again, help works very similar to Lisp. For any object (even created during the session), that has a docstring, help will show that docstring.

That is why I am saying, syntax aside, both languages have a lot in common.
Both inspired on Lisp (Clojure naturally more), both with a foot in the OO world (Python naturally more).

2 Likes

My experience with Python is limited. I have used it when necessary, but it’s never appealed to me. So I have been relectant to contribute much to this discussion, but one claim about the two languages that’s been made has gone unchallenged yet keeps bothering me:

Is it really true that Python is intrinsically easier to learn than Clojure?

FP is unfamiliar to many programmers, OO is familiar to many programmers, and lisps are unfamiliar to many programmers, so it may be that as a practical matter many programmers will find Python to be easier than Clojure. If that’s what was meant by earlier comments that Python is easier, then I’m sure it’s correct.

However, I wonder whether apart from the fact that everyone learns imperative and OO styles first, is there is an intrinsic ease advantage to Python? (This really is a question, and not an argument, since I don’t know enough about Python. I know that it’s a matter of opinion in the end, but I would find a little bit of friendly discussion illuminating if anyone’s interested.)

Caveat: Clojure error messages are still so noisy that I’m somewhat reluctant to recommend it to people who aren’t already experienced programmers. Python has to be better on this dimension.

3 Likes

Having gone the OO->FP way, I‘d love to read more from people who started out programming without learning imperative/OO first. I‘m sure there‘s something to your point, @mars0i, as I remember most of my learning Clojure consisted of unlearning OO taxonomy and constructs (which is still an ongoing process).

1 Like

I don’t do much with Python and haven’t done more than write toy programs to learn the basics. I had a friend who took some programming classes and wanted me to look over their projects to give pointers or help when they got stuck. Their Java code was practically unreadable and I had a hard time following it. They then switched to Python. I could follow their code easily and spot errors without even needing to run it. My point being, even a new coder who is known to write unreadable code in general, still writes easily readable code in Python. Python is basically pseudo code that runs.

Compare that to something like Lisp/Clojure. I’ve been writing software for 20 years, using more than half a dozen languages. I use FP style as much as I can because I prefer it for most things over OOP. Javascript, for example, I tend to write FP and never use the class constructs. I still can’t follow Clojure code and every time I try to read some open source code there’s several spots I just look at and think “WTF does that do???” So, I’d say that yes, Python is much easier to learn than Clojure. It’s almost like the difference between learning a GUI versus learning the Unix command line.

It’ll be hard to find somebody who doesn’t learn imperative first, given that computers are inherently imperative. I’ve wondered the same thing, though, if somebody would find it difficult starting with FP and moving to imperative. I’m not sure they would. My problems with FP languages haven’t been the FP concepts, it’s been the cryptic, confusing names of things and their weird tendency to use operators that end up making the code look like line noise.

2 Likes

Answering to anyone talking about data science/engineering, you might want to take a look at the https://github.com/techascent/tech.ml stack: you get “dataframes”, modeling capabilities and a lot of other stuff as well!!

2 Likes

@mars0i I think you make a great point. As a fun story, Clojure was my first Lisp. At that point, I was an expert at OOP, imperative and procedural languages. Especially typed ones, C#, Java, C++. Python was my dynamic language of choice, for small scripts and what not. First trying out Clojure, it was really hard to read and understand anything. Even my eyes could not see beyond parenthesis, the whole file just looked liked garbage random characters. As I started to make sense of the parenthesis, characters, and overall structure, I then struggled quite a bit with the semantics. How do I do anything? How to I organize and model my problems? Or finding code that did things in ways that I couldn’t follow, because it used modeling techniques that blew my mind. Fast forward 3 years later, and now I feel quite comfortable with it all, and I’d say it is all familiar to me now, equally so then OOP, imperative and procedural (in fact, I started to forget parts of these :stuck_out_tongue:). Now that I’m familiar with Clojure, I’ve been easilly able to pick up Common Lisp, Emacs Lisp, Racket, etc. Looking at code from these languages is familiar, I know how to maneuver my way around, and can quickly figure it out. Semantically, there’s even things that make more sense to me now, like using Java Streams seem simpler and more obvious then a loop. So for example, my Emacs is now full of custom Emacs Lisp I wrote myself, and I can easily debug Emacs code. Basically, I feel there’s for sure some truth to what you are saying. Familiarity plays a huge role, that’s hard to deny. Even going back to how we learn math is biased against Lisps, and plays into making it more alien.

I agree with names. Actually, I find Clojure does a better job then others, you know, at least first is called first and not car. That said, there’s no operators in Clojure, so that one isn’t an issue.

I was like that when I started as well. Like you said though, you have 20 years of familiarity with Python like languages. I think it is very hard to distinguish what part that contributes to the ease of learning it, versus learning Clojure.

I would say though, if you think in terms of how many things are there that one can use in Clojure vs Python to accomplish a task. There’s so much more in Clojure, and there’s not even really any “best practice” in terms of which way is best. Its like, here’s an infinite tool-belt of tools, all of which are pretty damn multi-purpose, and for every task, there’s going to be more than one (and possibly 5+) different ways to do it. On top of that, it gives you tools to build more tools! Where as Python has always been about “there is only one and only one way to do anything”. And in general, procedural, OOP languages have less tools baked in I’d say. So in that sense, you could say it would appear Python would be easier, in that, the surface area is just smaller, so you can more quickly learn it all.

That’s very true, and I’m not sure how to address it exactly. I guess I need to write more tutorial, but the longer it gets, the further I feel I’m able to explain Clojure to new-comers.

2 Likes

That is true about names, Clojure is better than others. The other FP language I started to look at was Haskell and didn’t get very far. I guess in Clojure they’re not called operators, they’re macros. ->, ->>, #(), ^{}, >:, <:, etc.

I don’t think it’s very hard at all. My first language was C/C++ and that’s probably the 2nd most used language for me in my lifetime behind Java. I recently tried to look through the XOrg source code and it took me a full week to figure out where they put main(). When I’m learning a new language, the first thing I do is try some of the toy, academic stuff on Hackerrank. I looked through some solutions that people had done in C or C++ and couldn’t even walk through their algorithms. Java code has gotten like that since the introduction of generics and is even worse now that everybody’s addicted to Spring. Python, though? It’s like going from Shakespeare to Cliff’s Notes. It’s such a simple, plain, straight forward language.

Like I said, I get the concepts of Clojure. I fully grasp what the language is doing, but I have a really hard time following it. It requires a lot of rote memorization of minor differences between similar things. Why does swap work here, but reset doesn’t? What do vector and vec do differently? Why is the map function called map? What is getting mapped? If map does what it does, what’s the point of apply? Isn’t map applying a function to the list? That’ll make anything more difficult to learn. Python’s not like that. Not by a long shot.

My experience with Lisp started in the early 2000’s. I wanted to try it because I heard how great it is and will make you better, etc. At the time I was doing a lot of client/server stuff, so decided I’d write a client for the protocol I was working on at the time. Well, there’s no sockets in Lisp. Found out there’s no GUIs, either. There were compilers that had added that stuff, but none of it was standard and the compilers that had it were commercial and cost money. Game over.

Then I heard about Clojure and decided to try it last summer. I went my normal Hackerrank route and very quickly ran head first into the loop performance problems. After going down the rabbit hole of type hints, different loop options, etc, I gave up on it and decided I didn’t really trust what it was doing.

Recently, I found out about Clojurescript. I’ve been doing a lot of stuff in Javascript lately and am pretty well Javascript’ed out. It’s a very annoying language to use on a regular basis. So, I’m hoping Clojurescript is a good option to write web stuff without having to deal with actual JS. We’ll see how far I get this time. :slight_smile:

1 Like

Echoing a comment made by Sean Corfield in an earlier message in this conversation, can you take a running Python program, and connect to it from another program (e.g. telnet, ssh), and get an interactive Python session that lets you view/manipulate data in the running Python program? That is something many Clojure developers do in production.

I have used interactive Python sessions, and for at least brief exploratory development, find it similar in usefulness as the Clojure REPL for similar purposes. However, that is quite different than the use case Sean mentions, so was curious whether Python enables that.

1 Like

A selling point of Clojure is simplicity. Not just simplicity of the language itself, but simplicity of the software you can make with it, too.

Rich Hickey pointed out, in “Simple made easy”, that “easy” means close-to-hand. Easy/hard is relative to where you are, while simple/complex is objective. Because people can learn, ease matters mainly at first. By contrast, complexity dogs you for ever.

Allen Downey’s wonderful book, “Think Python”, touches on several complexities in Python. Some of them:

If you use more than one operator (+, *, etc) in an expression, you must be mindful of the order of operations. You are invited to code functions with multiple exits. Wherever you use mutable objects, you must be mindful of aliasing them. When you are reluctant to alias, you may choose copy or deepcopy (and you must choose where in the program to put that onus). Not all kinds of object can be keys in a hashtable, but being eligible to be a key does not guarantee that an object is deeply immutable. The serialization format (pickle/unpickle) is not human-readable and is not the same as program notation. Reloading code in the interactive environment can be tricky, so you are advised to restart it altogether. To make things “easy” for a user of your work, you may override operators (by implementing __add__ etc). If you are using objects that override an operator, you must be mindful that it might matter whether the object is on the left-side or right-side of the operator. You can tangle everything up with implementation inheritance …

That list reminds me of Java. Python may have lowered the barrier to entry, but once you get over that hump, your energy is taxed by pretty some of the same headwinds.

4 Likes

I guess my question to you would be: “If so, why is that?”

I’m not denying it, there’s obviously an impression of Python being easier than Java or C++, but what could be the reasons behind this impression?

The only thing I can think of is the language surface being smaller. Python has less constructs than C++ or Java, and maybe less concerns. It is interpreted, with dynamic typing, and most other things are a convention instead of actual constructs. I also know it tried really hard to go the “one and only one way” route, which in practice is done by not adding more constructs to the language and keeping the list of features short.

Do you see any other possible reasons?

If these are real reasons. It would follow Python is also easier to learn than Clojure, because Clojure similarly has more constructs and features than Python. I think it’s not too controversial to say that “more things to learn == harder to learn”.

What I’m more interested in though is how Clojure compares to similar surface area languages. Given other languages with a lot of concepts, constructs, and features, is Clojure more/less difficult to learn, and if so, why? Here, I still believe Clojure is probably just as easy/hard as any other equal surface language, except for familiarity which gives you a big head start, which most people won’t have when tackling Clojure.

Because it reads like English. The other fundamental philosophy it follows, aside from “one way to do everything,” is “readability matters.” It quickly overtook perl as the number one scripting language (once it was functionally on par) due to how easy it is for non-programmers to understand and write it.

The one thing you hear over and over again when coding is to use descriptive names. How well does Clojure do that? You need something that passes the output of one function as the first parameter to the next? What’s the best name for that? ->? Perfect!

For readability, this

(thread-first a b c)

would be easier than

(-> a b c)

because you only have to remember the concept, not the hieroglyph that’s used to represent the concept.

As an example, here’s some idiomatic Haskell code, which illustrates why it took me less than half an hour to decide I never want to code in that language,

main = print $ foldl (+) 0 [1..100]

I couldn’t tell you what the underlying algorithm is that’s being solved by that. The only thing that makes sense is it’s printing something and there’s a range at the end. That’s not because I lack an understanding of FP, it’s because of how the language is designed.

I would equate Clojure to straight C code. Minimal core language, macros to allow you to restructure the language and create DSLs, small set of core data structures, and useless error messages (Clojure might as well just spit out “seg fault (core dumped)”). Clojure might be easier to learn given that the data structures are more useful and you don’t have memory management issues.

C++ adds to the core data structures, but does so in the worst way possible. It then adds move/copy semantics to the memory management issue. I’d think that Clojure would be easier to learn than C++.

Clojure suffers from not having a killer feature or app. C/C++ are tough to learn, but they’re the fastest languages and allow you to get closer to the hardware. Java is getting more and more confusing by the minute, but it grabbed a foothold by being the first really useful language that got around cross platform compilation. JS is a strange language, but it’s the core of web programming so everybody is stuck with it. Python grew to prominence out of ease of use. Clojure (and Lisp, in general) doesn’t have anything that would force people to put up with the complexity. That’s why Lisp concepts are constantly being adopted by other languages but Lisp itself struggles.

Thanks for all of the comments on ease of learning Python vs. Clojure. The jury’s still out …

On whether Clojure might, in theory, be as easy to learn as Python–well I still don’t really know Python, but I have given further thought to what’s involved in learning Clojure. I now think that there isn’t a simple answer to a question about how easy or hard Clojure is to learn. It depends on what part of the language you’re talking about. Here are some illustrations, all my own opinions, of course.

Background: In other languages, assignment is a simple concept: Change the value of something–that’s simple. Arrays are simple concepts: how far into a sequence is it? Looping by incrementing an index is a little more complicated, but you already had to understand the basic tools you need for a simple loop. More complex loops are more complicated, but that can’t be helped.

Now for Clojure:

Functional updating of defrecords and maps: I think this is simple. It’s confusing at first if you’re used to assignment, but I think that’s an artifact of learning imperative languages first.

Functional updating of sequences, in general: I’d make the same point.

reduce is not a simple concept, and you can’t understand it without understanding all of its parts at once.

map is simpler, but still not as conceptually simple as assignment; you have to understand that the “same” thing, in some sense, will be done to each element of a sequence.

conj: This is a little confusing, because you have to know what you’re operating on to know where you’re adding the element, but the idea of adding an element to a sequence is utterly simple.

next vs. rest: Yeah, that’s confusing. But the underlying concept that they share is simple.

range and a large collection of sequence processing functions: iterate, repeat, repeatedly, take, drop, cycle, partition, etc. Most of these are pretty simple, and though some are a little bit complex, that’s just because what they do is complex. You do have to remember (or look up) the names, but I don’t see how one could have names that were much better, unless you want to replace the names with names such as

repeat-the-same-object-as-is-in-a-sequence
sequence-composed-of-results-of-applying-the-same-no-arg-function-repeatedly
sequence-composed-of-the-results-of-iteratively-applying-a-function-to-the-previous-element-of-the-sequence

Laziness: Definitely confusing–if not at first, then soon, when it bites you. And keeping track of whether you’re holding onto the head of a large sequence feels like keeping track of state in an imperative program. I’m not saying that it’s the same thing, but the mental energy required seems similar to me.

Recursion: I know this confuses a lot of people at first–I have known pretty good programmers who were afraid of it–but with a good introduction like The Little Schemer, it’s not hard to get the hang of it. And explicit, full-fledged recursion isn’t that common in Clojure, I think. loop/recur, as its name indicates, can be understood either as a loop or as recursion.

(I agree that function naming matters, but I personally don’t think Clojure is bad in that regard. Not perfect, but not bad. Compared to Common Lisp, Clojure is close to perfect. A lot of the useful functions do things that, in English, might be described in a similar way, so some arbitrariness is unavoidable.)

Morbid curiosity took over and I looked into this. It doesn’t seem like Python would do that, since it’s such a Bad Idea™. Turns out, no. It doesn’t allow that. You can explicitly code the behavior into your server by opening a new port that opens a console with the REPL, but it’s not something you can do out of the box.

Security 101 is “don’t open unnecessary ports.” Aside from that, it’s mind boggling to think that somebody would have their production servers open to a connection that can arbitrarily change the running code and do whatever DB queries it wants.

1 Like

That’s kind of the heart of the matter. The underlying concepts aren’t that difficult, but the way they’re expressed in the language is confusing. It’d be interesting to have somebody try to make an FP language that starts with Python’s philosophies of readability matters and one way to do things and see what happens. FP languages seem to pride themselves on dense, fewer lines of code. The result of that is they tend to be hard to read and confusing.

I am sure security-conscious people would not open such a port without encryption/password-auth/etc. to protect it.

All production servers are open to network connections that can change their running code and do whatever DB queries they want. Either that, or you cannot change the version of production software you are running, ever, yes?

I haven’t used this technique myself, but I know people who do, and I’m pretty sure they know the risks involved and don’t want to break their production servers while using it. They probably keep read-only vs. possibly-modifying function calls well in mind when investigating issues using this technique, or they know they get the troubles they ask for.

1 Like

I’m not sure I’m necessarily convinced by that. Also, if it were the case, that’s just back to familiarity.

What does that mean though? What makes something readable? Are we talking fonts, choice of characters, visual noise, etc. ? Or are we meaning good use of comments, literate programming style, simple logic, code well factored into logical pieces, etc. ?

I don’t know that I agree. One is a visual indicator of what it does. Its like an icon in UX. Take elevator buttons, they don’t say Go up and Go Down, they’re just an up arrow and a down arrow. Why is that?

Then, I might say, sure, the first time you see it, you have to look it up, but afterwards, what’s hard to read about it? It gets out of the way visually, which limits line noise.

For example, would you prefer:

if a greater or equal to b then do result equal a plus b and return result

I’m not sure that is more readable. Maybe it is self-describing. Like the definition and description of how it works and what it does is embedded in its representation, but I’m not sure this is always ideal, even for readability. For complex things, it might become too verbose, which can hurt readability in my opinion.

So how is it in Python:

print(sum(range(0, 100)))

And in Haskell it would be:

print $ sum [0..100]

I fail to see why Python reads more like English and is more readable ? In Haskell, you could also do:

print(sum([0..100]))

Now, if you want to do it as a fold instead, to me, it feels simply like not knowing the concept. In Python, you’d do:

print(functools.reduce(operator.add, range(0, 100)))

And in Haskell:

print(foldl1 (+) [0..100])

Again, I fail to see why the Python is more readable or English like.

Again, I think this is only due to familiarity. Bring one Lispy concept at a time to familiar languages, as opposed to make the jump from familiar to completely unfamiliar. But I’m not convinced there is anything inherently harder to learn in Lisps. I’m also not sure what the “complexity” you are referring to is in this case ?

Regards

2 Likes