Fork me on GitHub
#beginners
<
2020-12-19
>
andarp08:12:09

Is there like a support group to talk to about seeing - truly seeing - Clojure for the first time? As someone who haven’t done LISPs before, but have extensive C# experience as well as a great interest in typed functional programming, I never thought a LISP with dynamic (!!!) typing would be anything for me. But having just worked through a couple of Advent of Code challenges I feel obsessed with the language. I dream about it! Lovely parens everywhere! Watching some of Rich Hickey’s talks just jolts me with insights that challenge my last ten years of professional programming experience. Seeing static types as a lovely puzzle to solve, but oftentimes a hindrance to actually solving problems - 🤯 In many ways using Clojure feels like the first time I learned Python some 20 years ago. Everything feels exciting and challenging and possible. Things just make sense. The first time ever writing a single line of Clojure I completed twelve programming challenges in Advent of Code without getting the answer wrong a single time. No off by ones, no confused abstraction usage. No null ref bombs. Just data and (already familiar) functions, packaged up in a way that feels intuitive and easy to use. No “killer feature” that actually involves a lot of complexity (looking at you, Go channels) and leads beginners down a path of power but also confusion.

👍 36
rich 9
emilaasa11:12:16

I know the feeling! 🙂

valerauko13:12:12

Clojure actually has async channels though

simongray13:12:21

Welcome to the cult. Types are overrated.

didibus18:12:55

> No “killer feature” that actually involves a lot of complexity (looking at you, Go channels) and leads beginners down a path of power but also confusion Well 😅, actually there are some of that, but lets say they're kept away for advance power users. You've got the same complex channels and CSP Go-routines, actually probably even slightly more complex than in Go with core.async. And you've got user defined macros I'd say are an easy way for beginner to get themselves in a place of too much power that confuses them. But yea, the language steers you towards simple things, that's really engrained to its core. So happy you're enjoying it! I've had the same happen to me, and now no other language brings me Joy anymore, only Clojure or Clojure-like languages do 😛

Panagiotis Mamatsis10:12:27

Hello everyone! I hope my message finds you and all your loved ones well and healthy! I am in Clojure for three months now. Of course i am still a complete n00b but i wanted to ask a question...how Clojure compares to Scala? I mean they are both functional but any differences?

flowthing12:12:29

Plenty of differences. I'd say that the only similarities are that they are both functional languages on the JVM. Scala is a statically-typed language with a powerful (and complicated) type system. It's also a multi-paradigm language. That means you can use in both imperative and functional style. Most Scala users learn towards the functional style, in my experience. Clojure, in contrast, is a dynamically-typed language. You can use it for imperative programming, but very few people do that, and the language heavily steers you toward functional programming. Clojure is also a Lisp. Clojure developers typically make heavy use of the REPL to interactively iterate on their programs. Scala is more traditional in that sense, relying heavily on static typing and IDE support. Clojure is a vastly simpler language than Scala. See also the Clojure rationale at https://clojure.org/about/rationale.

👍 3
flowthing13:12:50

Oh, and importantly, Clojure puts data first, whereas Scala is more reminiscent of the traditional object-oriented approach in that respect.

👍 3
Panagiotis Mamatsis13:12:11

Hi @U4ZDX466T and thank you so much for your response! My background is mostly JVM languages. For the last 4 years i am working on Groovy language. I have touched a bit of Scala but never truly kept me due to the complexities that you are referring. I felt that i wanted more power and more simplicity! That's why i have chosen to start learning Clojure. I have a long way to! One more thing that hooked me is the ability of this language to do hot code swap. Something that, if i am not mistaken, only the BEAM languages can do.

didibus18:12:54

I think @U4ZDX466T explained the difference pretty well. I would say that in my experience, a lot of people actually use Scala in an imperative OO style, and not functional, because a lot of people choose it over Java simply to avoid the verbosity that Java has (or used to have, since its improved a lot in that area more recently). This isn't always true, die hard passionate Scala devs do use it mostly functionally, but in my work experience, places that are JVM shop (and not Scala shop specifically), where you have some teams or services that tried Scala, often end up using it in OO style. Clojure on the other hand can't be used like that. So its both a harder transition, as well as a better introduction to FP in my opinion.

didibus18:12:45

Clojure can do hot-code reloading, and dynamic code loading as well. But I'd say, in Erlang, you might realistically (though even in Erlang its not really done), deploy your code changes to prod using hot-code reload. Where as in Clojure, you probably shouldn't deploy changes with hot-code reload to prod, but you will definitely use it for development, testing, and debugging.

didibus19:12:08

Oh, one more difference with Scala, I have found, Clojure has much better interop with Java, where as Scala's interrop requires a lot more hoops, so I find Scala to be more of an island.

andarp13:12:42

Is Clojure somewhat slow on doing lots of mathematical calculations? Currently I'm summing 500k vectors of ~20 numbers and that takes almost a second in total. Is that expected performance or am I doing something wrong? Essentially just mapping (reduce +) over a for-comprehension. This code takes 100 ms to run in my REPL, so I'm guessing I'm doing something weird in my original code...

(time (reduce + (map (partial reduce +) (map #(range % (+ % 25)) (range 500000)))))

Ben Sless13:12:21

that's the first thing to be aware of

Ben Sless13:12:35

but we can do some more digging

Ben Sless14:12:31

The code is very functional but slightly abuses lazy sequences, can we eliminate some of that overhead?

Ben Sless14:12:15

You'll gain a slight speedup by dispatching directly to more specific methods. Def-ing something like

(defn qadd
  ([] 0)
  ([^long x] x)
  ([^long x ^long y]
   (unchecked-add x y)))
and using it instead of + will already improve performance

Timur Latypoff15:12:50

I agree with @UK0810AQ2. As far as I understand, for best performance, intensive numeric calculations should be done in a tight (loop [...] ... (recur ...)) within a single function (Clojure fns box their args) with typed non-boxed locals and typed arrays (no lazy sequences, no Clojure vectors since they store boxed Objects).

☝️ 3
Ben Sless15:12:14

Definitely, a loop would be best, I tried not to break the initial mold too much. All the ranges do take their toll

andarp15:12:31

@UK0810AQ2 Sorry for disappearing, my daughter woke up and then real life happened. Thanks for taking the time. Good to know that there’s an escape hatch when that type of performance is needed!

andarp15:12:15

My assumption was that laziness would help speed up the process in terms of not having to realise all the sequences in memory before performing computation. Obviously that’s not a correct assumption based in your input...

Ben Sless16:12:12

@U01GZD00HA8 I'm in a similar situation, don't feel pressured to synchronously reply 🙂 The full answer is that like in every case, it depends. You could create a 2d array and sum it, that would be fastest. Searching on both dimensions of laziness and allocations until we find an optimum solution is an interesting exercise, for which results may vary depending on anything from your implementation, to your JVM version and choice of GC algorithm

andarp16:12:10

Thanks a lot for all the input! I don't really need to solve this specific performance issue, I just wanted to understand what I could do if this issue crops up in a real case.

andarp16:12:14

The boxing explains a lot, though. As long as it’s possible to avoid it and optimize for speed over safety or ergonomics in the few cases where it’s needed I’m happy.

👍 6
Ben Sless16:12:26

There's nothing wrong with having a few scary, well tested and non-idiomatic functions which deal with unboxed data and perform black magic

Ben Sless16:12:06

But there's always a risk if anyone besides yourself might use them

dorab17:12:47

For a slightly more advanced example, and a very good explanation of the process behind such optimizations, take a look at a recent conversation on clojureverse (in particular, the answer by joinr) https://clojureverse.org/t/my-recent-clojure-vs-c-experience/6909/3 Though, doing unboxed math, using arrays, using loop/recur and not using lazy sequences will get you far. For extremely fast performance (CPU and GPU) take a look at https://neanderthal.uncomplicate.org/

☝️ 3
clojure-spin 3
dorab17:12:50

None are really beginner topics, but (as you said) good to know they exist.

andarp18:12:21

Thanks a lot! Will take a look and back out when my head starts spinning.

andy.fingerhut18:12:30

The normal Clojure vectors you get by default contain all boxed values, but there are also Clojure vectors restricted to one JVM primitive type, e.g. (vector-of :long 1 2 3) that are more memory efficient, and I would guess are able to get one closer to the speed of iterating through Java arrays of restricted types, but I haven't compared performance between those two approaches.

😮 3
didibus19:12:41

Everyone else assumed that you're hitting the bottlenecks of boxed math. But I don't think that is what is happening in your case. Are you sure you're not measuring the startup time as well?

didibus19:12:16

But, if the slow-down is because Clojure persistent data-structures and sequences do boxing of primitive types, I recommend this article: https://neanderthal.uncomplicate.org/articles/fast-map-and-reduce-for-primitive-vectors.html If you really want fast numerical computation, and I mean, faster than Java fast, Neanderthal and Fluokitten are what you want!

❤️ 3
alexmiller18:12:26

it's undocumented because that's not a thing

hiredman18:12:58

Is it one of those cljs things?

alexmiller18:12:10

oh, could be, don't know

alexmiller18:12:28

you can extend protocols to Object or nil to cover default cases for those in Clojure

👍 3
hiredman18:12:10

Cljs in a few places supports variations of 'default' or ':default' for types because of the type hierarchy in js

clyfe18:12:00

Sol yeah, probably a cljs thing then

alexmiller18:12:31

might be worth posting on https://ask.clojure.org as a request for doc improvement (or https://github.com/clojure/clojurescript-site/issues for web site doc)

clyfe19:12:38

> You can implement a protocol on nil > To define a default implementation of protocol (for other than nil) just use Object

clyfe19:12:48

Seems it's already on the website.

hiredman18:12:37

Definitely cljs

alexmiller18:12:57

not sure which or both are most useful

dpsutton18:12:13

It’s in the doc string of extend type in cljs it seems

dpsutton19:12:56

And it’s quite clear in its wording

clyfe19:12:25

ty all, apologies for the wave

dpsutton19:12:41

no worries. i'm glad i looked. had forgotten about that

dpsutton19:12:11

and i didn't mean to sound like "it was a dumb question because its right here" but more "its a great question and i didn't know and here's how i answered it"

❤️ 6