Fork me on GitHub
#off-topic
<
2022-10-14
>
pavlosmelissinos13:10:12

Has anyone tried to compare Clojure vs Python design choices? I'm around both at work, so I can see how some of the same problems have been solved differently but I'd like to see a summary of them if one exists. Stuff like middleware/interceptors vs decorators, destructuring vs unpacking

👍 1
andy.fingerhut14:10:33

Wouldn't something like "immutable data is default and idiomatic in Clojure and its libraries. It is available via Python libraries, but not the default and you might be swimming upstream to use it." be near the top of the list of comparisons between Clojure and most default-mutable languages?

1
pavlosmelissinos14:10:49

Yeah that seems to be an obvious design difference but I think I'd be more interested in a compilation of more specific examples: "here's a problem that applications in both languages have to face, this is how Clojure solves it vs how Python deals with it"

Dimitar Uzunov14:10:10

Clojure has real concurrency in the language and runtime, python projects work around it by using OS threads and services like celery to simulate it

Dimitar Uzunov14:10:31

Just a way in which your language may influence your tooling

Dimitar Uzunov14:10:41

for some orgs that use scripting languages dividing up the project into microservices is also a solution to the lack of support for concurrency.

Martynas Maciulevičius14:10:27

I once heard that when python designers talk about their Global Interpreter Lock then they say that "when you do concurrency in-process then you'll eventually run out of RAM/CPU and will have to redesign the app." This is their argument to use multi-process concurrency from the start.

🙂 1
Noah Bogart14:10:48

Clojure has lexical scoping, Python has… something els

pavlosmelissinos15:10:43

concurrency in Python is a mess indeed 😄 > Clojure has lexical scoping Oh, that's right! Perhaps similar to this, I've tried to simulate the REPL workflow in Python in the past but ran into issues with how modules work; they don't seem to be properly separated and the Python interpreter couldn't properly "reload" code or disambiguate similar names between modules 🤷 But I digress, I didn't want this to be a thread where we bash Python. I'd like to: • be able to discuss/debate with people who think Python is ok but are skeptical about investing further in Clojure • appreciate Clojure's design even more 😉 • maybe even realize that there's room for improvement! • help my Pythonistas coworkers/friends with the problems that Clojure has solved pretty well and can be handled better in Python

👍 1
Dimitar Uzunov15:10:39

Python at least in my experience is a bit finicky in getting your project setup - quick start is ok, but getting some established project to run is not that easy. Things like pip don't seem to be working flawlessly. Then you need to ship. Clojure doesn't seem to be as hard, maybe I'm biased but I've never had trouble running clj and lein projects + plus the uberjar makes deployment easy without resorting to docker. I wonder if you agree or not @UEQPKG7HQ :)

pavlosmelissinos15:10:37

Well, I mostly agree but the Python experience can be improved a lot with tools like poetry (pipenv is supposed to be similar but I haven't used it), so it's not a huge difference, I think.

andy.fingerhut15:10:43

I really like Python. For single-threaded applications that glue together major C libraries, or do simple text file hacking, it is my go-to language even though Clojure exists. That said, I'm still a hobbyist when it comes to both Python and Clojure, in terms of how often I use them.

eggsyntax16:10:51

> I've tried to simulate the REPL workflow in Python in the past but ran into issues with how modules work; they don't seem to be properly separated and the Python interpreter couldn't properly "reload" code or disambiguate similar names between modules Agreed it shouldn't be a bash-python thread, but this is something I've been really struggling with recently as I've been doing some work on a python project. Once you're used to the Clojure experience of having a REPL that's in a specific context inside your running code, the limitations of the python shell are frustrating. The closest thing I've found is using https://docs.python.org/3/library/pdb.html, which is in a specific context but doesn't have the full capabilities of a python shell. I'm going on about it here because if anyone has suggestions on better options for approaching a full in-context REPL, I'd absolutely love to hear them. I haven't dived fully back into building python expertise, so I wouldn't be surprised to hear that there are tricks I'm missing.

pavlosmelissinos16:10:43

> I really like Python. For single-threaded applications that glue together major C libraries, or do simple text file hacking, it is my go-to language even though Clojure exists. > That's another good point. I used to have a combination of bash and Python scripts for small-scale hacking, nowadays I usually reach for babashka first. Python users don't need a "babashka", their default runtime fires up quickly enough and hacking implies a lighter workload so the performance issues of the runtime are not important

eggsyntax16:10:01

> I'm going on about it here because if anyone has suggestions on better options for approaching a full in-context REPL, I'd absolutely love to hear them. That triggered a fresh round of google searching, & it looks like you can directly https://stackoverflow.com/a/1396386 and/or use pdb's https://docs.python.org/3/library/pdb.html#pdbcommand-interact to do the same thing. Will kick the tires on those some today.

pavlosmelissinos16:10:31

> I'm going on about it here because if anyone has suggestions on better options for approaching a full in-context REPL, I'd absolutely love to hear them. I haven't dived fully back into building python expertise, so I wouldn't be surprised to hear that there are tricks I'm missing. > @U077BEWNQ If by any chance you're an emacs user, this is the closest I've managed to go: https://github.com/PavlosMelissinos/dotfiles/blob/master/.config/emacs/init.el#L971-L1109 It's far from ideal but it sort of works (sometimes the names collide if I make a lot of changes and I have to restart the shell but other than that it's the best workflow I've had in Python). I haven't used it in a while but I'm happy to tell you more about it if you have questions.

🙏 1
didibus01:10:15

Python and Clojure are very different, it's hard to exhaustively compare them. Some thoughts on the top of my head: • Clojure is compiled to JVM, and from there either JITed or AOTed to native. Python is interpreted. • Clojure can use any Java, Scala and Kotlin library. It can also use C libraries, and therefore funnily enough a lot of Python indirectly. Python is restricted to only Python, and some C libraries explicitly wrapped to be exposed to Python. • Clojure is a Lisp, Python has its own unique syntax that's indent sensitive • Clojure is a functional programming languagenfor the most part, Python is an imperative programming language for the most part • Clojure relies heavily on immutable data-structures to model program state, while Python relies more on classes and objects with some mutable data-structures here and there • Clojure can also be compiled to JavaScript, or to .NET IR, or to Beam bytecode, or to Dart. As far as I know Python can't, though it can be interpreted over Graal with Truffle Python • Clojure development is normally done interactively at a REPL using an REPL connected editor. Python development is normally done either in TDD style, or more traditionally as a edit -> run program -> shutdown program, edit, rerun, etc. Notebooks are a bit of a in-between, but can't really be used for full application development. Clojure also has notebooks, though not as useful since REPL is already so interactive. • Clojure can be extended by the user in syntax and semantics using macros. Python cannot, though there are some AOP support with decorators, they're more like extension points then full extensibility. • Clojure code can be structurally edited with paredit or pareinfer, Python cannot • Clojure supports full blown multi-core and multi-threaded concurency and parallelism, Python doesn't. • Clojure supports Concurent Sequential Processes like Go, Python does not. • Clojure with newer JVM 19 supports full blown lightweight processes like Erlang, Python does not. • Clojure is homoiconic and supports its own data notation format in EDN, similar to how JavaScript has JSON, Python does not have anything like this. • Clojure is more performant than Python, but doesn't start as fast. Python starts faster, but doesn't perform as well. • Clojure can also be interpreted using Babashka, in which case it starts and performs similar to Python. • Clojure can be compiled to native using GraalVM native compilation, in which case it starts and performs much faster than Python. • Python has way more libraries available to itself, especially in the data-science or ML space, but also for general scripting. Clojure must often rely on Java libraries to fill the gap. • Python has easier APIs in general and single name imports that are easy to remember and named after the thing you want. Clojure APIs are less intuitive, even though they're simple and compose very well, and the organization of where things are is a bit confusing and you always have to know the full qualified path to anything. • Python has implicit global dependencies so you install some library on your machine and can instantly use it from any Python project (though that becomes it's own issue for production use-cases). Clojure has explicit dependencies, you can't just install libraries somewhere either globally and locally and have Clojure find them there by convention, you always have to give the explicit path to each dependency every time you run a Clojure program. • Python is installed by default on Linux and Mac, and there's only one vendor. Clojure must be installed on all OS, as it does not come pre-installed like Python. Clojure needs JVM which has many vendors and can be confusing which one to choose or install.

💯 2
👍 2
Cora (she/her)02:10:38

I mean if you're using python you could use hy

Cora (she/her)02:10:57

it feels to me that python confuses simplicity in parsing with simplicity in language.

☝️ 1
pavlosmelissinos07:10:30

Hy is nice but it sits in an awkward middle ground, it's as slow as Python and as arcane to Python users as Clojure, so I think it's hard to find a niche in real-world application. It's also not exactly Clojure (last I checked it didn't have a let). It's a lisp though and the interop with Python code is indeed great! > it feels to me that python confuses simplicity in parsing with simplicity in language. > Can you elaborate on the simpler parsing part? I never thought Python would be considered simpler to parse than Clojure code.

Mno11:10:37

Simpler for humans to parse, more legible I assume

👍 2
mauricio.szabo03:10:32

@U0K064KQV good points but... > Clojure supports CSP like go... > Well, kinda. It's a library, relies heavily on macros, it stops working when there's a function declaration (either implicitly or explicitly). There's also python CSP, which have not been updated since 2012 but we'll, it's a library too. > Interpreted with babashka,... performs similar to Python > Well... no. Babashka is way slower, unless you don't abstract too much (like, write sequential programs without any defn, require, or things like that). I would also add that Clojure is focused on being backwards compatible as much as possible, where Python is not (and in fact, you need virtualenv or some other dependency manager to use some libraries that only work on specific versions)

blak3mill3r04:10:00

Clojure's adherence to backwards-compatibility (a.k.a not breaking working stuff) is unparalleled in the entire software ecosystem... like, nobody has ever manifested this goal as well as the Clojure core team. I have code that I wrote with Clojure 1.2 that still works exactly as it did (but faster) with the latest release.

💯 1
Daniel Tan14:10:39

im using Hylang for Python and I think aside from mutable vs immutable etc, there's also the difference in package management, painfulness of handling async in python, and how clojure data structures and algorithms benefit from a cleaner fp design, though just by using lisp-like syntax im naturally guided to do FP

Daniel Tan14:10:13

its far easier to work with AI in python tho, really painful to get stuff working in java

didibus17:10:01

I consider Clojure's CSP production ready. I'm not sure being it only has stackless coroutines means it's not a proper CSP implementation. Lots of languages only have stackless coroutines, like JavaScript, C#. Also since Java 19 you can make it stackfull as well, by having it use the JVM's stackfull threads instead. I didn't know Python had one as well though. How is it implemented? As a code pre-processor? Does it do M:N parallelism?

didibus17:10:07

I had done some simple benchmark of Python vs Babashka, and had seen similar ballpark results a while back. But it was very simple stuff. I was actually surprised, cause I'd assume the Python interpreter has had so much more man-power on it, that it should be a lot faster. I wonder if there's been more thorough benchmarks done. Have you done some yourself @U3Y18N0UC?

mauricio.szabo15:10:37

@U0K064KQV I did some extensive benchmarks with some more complex code because I wanted to see if SCI would be viable as an alternative for a project I was trying. I benchmarked SCI, and that was really not good, so I thought Babashka could be better. I remember it was on the order of 4x, maybe more, slower than Lumo, and Lumo was already quite slower than Python for example. I mean, Babashka's performance is quite fine for lots of things that are IO-bound, like an alternative shell script language and such, but Babashka is 100% interpreted - no AoT, no JIT, no bytecode at all. In fact, I'm amazed at some tricks that allow the code to kinda "bypass" that performance hit and delegate the code to Clojure (so that it becomes almost as fast as Clojure itself) but if you add too many abstractions it take a performance hit

eggsyntax15:10:44

> I benchmarked SCI, and that was really not good, so I thought Babashka could be better. I remember it was on the order of 4x, maybe more, slower than Lumo... > if you add too many abstractions it take a performance hit @U04V15CAJ curious whether those match your sense of sci / bb performance and whether you expect big gains in the future.

borkdude15:10:02

SCI has undergone significant performance improvements in the last year, so make sure to keep your benchmarks up to date. Also feel free to suggest spots where it can be made more performant.

👍 1
borkdude15:10:38

SCI will always be slower than Clojure itself, the benefit is really that it can be advanced compiled along with other libraries so you have faster startup in both JS and graalvm native-image. So the sweet spot is glueing clojure stuff together with functions: scripting.

2
borkdude15:10:00

If bb becomes too slow, use Clojure.

borkdude15:10:47

I'm also working on #C03U8L2NXNC and #C03QZH5PG6M which are CLJS compiler(s). You can make compiled code a lot faster than interpreted, but there are trade-offs between these approaches.

👀 1
borkdude15:10:12

Still I want to squeeze the last bits of performance of SCI where I can

borkdude15:10:07

bb is made to compete with bash: it's a lot faster than bash right now

eggsyntax15:10:03

That all makes sense to me -- there's definitely a huge sweet spot for bb, at least for me, and as you say it's very much as an alternative to the shell languages. Thanks!

borkdude15:10:13

SCI is slower with loops than compiled languages, but I just compared python:

$ time bb -e '(loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val))'
10000000
bb -e    0.59s  user 0.02s system 99% cpu 0.619 total

$ time python3 /tmp/loop.py
10000000
python3 /tmp/loop.py   0.79s  user 0.02s system 85% cpu 0.950 total
bb seems significantly faster here

👍 2
borkdude15:10:28

The python program:

x = 10000000
val = 0
while (x > 0):
    x = x - 1
    val = val + 1
print(val)

Noah Bogart15:10:49

You’re using the time macro internally vs the bash time function

borkdude15:10:00

@UEENNMX0T already fixed that

👍 1
borkdude15:10:34

(Both are on an M1 Macbook Air)

borkdude16:10:07

@U3Y18N0UC Next time you're doing a benchmark, feel free to reach out, I might be able to explain where the differences come from (and if those can be improved)

borkdude16:10:11

With a function call thrown in:

$ cat /tmp/loop.py
x = 10000000
val = 0

def foo(x):
    return x

while (x > 0):
    x = x - 1
    x = foo(x)
    val = val + 1
print(val)

$ time bb -e '(defn foo [x] x) (loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (foo (dec cnt))) val))'
10000000
bb -e    0.78s  user 0.05s system 91% cpu 0.906 total

$ time python3 /tmp/loop.py
10000000
python3 /tmp/loop.py   1.27s  user 0.02s system 99% cpu 1.292 total

mauricio.szabo16:10:03

Unfortunately, I don't have my original benchmarks anymore, but I'll be sure to reach out 🙂. I mean, one of the things is that I don't really need too much performance on SCI, so it is mostly a non-issue for me 😄

mauricio.szabo16:10:50

I do feel people put more emphasis on "raw performance" that needed too... for me, the simple fact that SCI exist, specially on ClojureScript, is amazing by itself 👍

1
borkdude16:10:52

Hehe thanks :) The similar loop example with squint (compiled JS) used directly from nbb: https://twitter.com/borkdude/status/1578338846806224896

mauricio.szabo16:10:08

BTW, I know these are not good benchmarks, but I adapted one of the old "Computer Benchmark Shootout" for BB / Lumo / Squint and Cherry: https://gist.github.com/mauricioszabo/70de5ad64de0aaa9e5c50f3b84759fdf. It's really amazing how different coding styles make a difference on performance, and also how good cherry is looking on these 👍

borkdude17:10:42

Yep, this is mostly interpreter vs compiler performance

didibus17:10:14

I find against Python and Ruby are good benchmarks most likely, since both are interpreted as well in their standard implementation.

borkdude17:10:54

(@U3Y18N0UC I get 7.594 total for bb for the with-vectors example, which version of bb are you running?)

borkdude17:10:02

(for nbb I got 15.431 total)

borkdude17:10:53

$ node -v
v17.8.0
$ nbb -v
nbb v1.0.137
$ bb --version
babashka v0.10.163

mauricio.szabo19:10:03

> both are interpreted as well in their standard Well, not really - Ruby and Python have JIT in their VMs too. Ruby 1.8 was the last version where the code was 100% interpreted

mauricio.szabo19:10:40

╰─>$ node -v
v14.17.1

╰─>$ bb --version
babashka v0.10.163

didibus19:10:05

I thought YJIT for Ruby was still an experimental feature and disabled by default. Also, where did you see CPython being JITed?

didibus19:10:20

Maybe arm64 vs Intel behaves very differently, could that explain the difference you're seeing?

didibus19:10:27

I really can't seem to find anything pointing to CPython being JITed, and for Ruby 3+ I'm not able to find if MJIT is on by default or not, pre 2.6 it was definitely off by default. And YJIT is definitely not on by default.

mauricio.szabo19:10:15

@U04V15CAJ just made a new benchmark with a version that I optimized for BB - it seems that avoiding lets make it run in half of the time wow

1
mauricio.szabo19:10:24

@U0K064KQV Sorry, it seems I made a mistake on my acronyms 😞. What I meant was that Ruby and Python are not "interpreted" in the sense of "getting each instruction and running it when it appears". They do convert to some form of intermediate representation / Bytecode / VM instructions and then run these, and this optimizes the speed of the languages by quite a lot. I thought this process had some specific name/acronym, but seems that I was wrong

mauricio.szabo19:10:05

It's different from, for example, Ruby 1.8 where the code was truly interpreted - in the sense of "read a instruction, make an AST, interpret the AST"

borkdude19:10:38

@U3Y18N0UC Ah, let is one of the things that I'm thinking about optimizing :) And that is quite easy to do... let me get back to you when this is done (probably after my Dutch ClojureDays talk)

borkdude20:10:03

doseq also produces pretty nasty code in Clojure

borkdude20:10:46

@U3Y18N0UC doseq often can be sped up by replacing with (run! #(...) coll)

mauricio.szabo20:10:55

In this case of my benchmark, it does not make too much difference - the coll is basically a 7-element range 🙂

mauricio.szabo20:10:45

Although, it does make the code run (and fail) on Cherry Squint. But changing the wrong things gave me 352ms`, the fastest of all implementations :)

borkdude20:10:16

@U3Y18N0UC does adding vec make it work and does it create the correct answer?

borkdude20:10:01

oh cherry, I thought squint

borkdude20:10:11

please file issues to cherry or squint when you find bugs

borkdude20:10:15

if you want to have them fixed ;)

mauricio.szabo20:10:10

Oh, sorry, it was supposed to be squint indeed

didibus20:10:29

Ah yes, they do have an IR, but it is being interpreted itself. I'm guessing it avoids some parsing by making it easier to interpret, or enable the interpreter to be a bit more optimized in some places. I feel that's still a fair comparison against BB, in that it's just about optimizing interpreted code, but still interpreted.

mauricio.szabo23:10:40

> to be a bit more optimized in some places Ruby 1.9 was, in average, 3x faster than 1.8, with some programs gaining up to 10x performance gains. I'm honestly not a compiler expert (not even a beginner) so I can't tell you why this happens, but it does happen - almost all "interpreted language" nowadays generate some kind of IR to feed that to some kind of VM for performance gains.

didibus23:10:22

Ya, I'd be curious to know how it helps. From what I was able to find, it did mention it seems to mostly optimize parsing. A lot of languages need look ahead to parse for example, for example if they support forward declarations. I can also see certain things could be resolved statically in that step, maybe some use of polymorphism for example, or method overloads. I wonder if a Lisp is as affected, because the syntax is already so convenient for parsing. With Ruby, I don't know if it's all a result of the bytecode though, cause they reimplemented it all, it switched from MRI to YARV. There could have been many more changes in the implementation to speed it up, not just introducing an intermediate representation.

borkdude10:10:09

Optimizing let in SCI. Already at 8x faster. https://twitter.com/borkdude/status/1582310162202648577

👍 1
borkdude10:10:27

@U3Y18N0UC The with-vectors example with let optimizations (locally): New:

5.558 total
Old:
7.670 total

borkdude10:10:51

(so the impact of 8x faster let bindings doesn't speed up the overall program that much, but it helps somewhat: 1.4x speedup)

borkdude10:10:30

I'll release a new nbb with this

borkdude10:10:54

nbb 1.0.138 (now released): I see a 16s -> 10s speedup (1.6x) with the with-vectors example

borkdude11:10:21

There is a trade-off here though. SCI spends more time in analysis, but runtime performance becomes faster. E.g.:

$ /opt/homebrew/bin/bb -e "(time (require 'honey.sql))"
"Elapsed time: 30.896125 msecs"

$ bb -e "(time (require 'honey.sql))"
"Elapsed time: 38.958083 msecs"
So the previous version loaded honeysql significantly faster.

borkdude17:10:22

@U3Y18N0UC I released bb 1.0.164 now. Welcome to try it out

borkdude17:10:19

let will still have some overhead, but not as much anymore

🎉 3
mauricio.szabo17:10:42

@U04V15CAJ did you bump a whole major version? 😄

borkdude20:10:42

Yes, this was already planned, but this was surely a nice addition ;)

😁 1
mauricio.szabo23:10:55

You went full "big-corp jumping versions" there :rolling_on_the_floor_laughing: