Fork me on GitHub
#beginners
<
2018-06-06
>
kyselyradek01:06:28

Hello everyone, I have a question that appears here at least twice a day (maybe time for the pinned post?)—how to get started with Clojure. I’ve actually checked out few guides on how to start but would like to still hear how you all started.

kyselyradek01:06:08

I’m proficient in Node.js (also done some infrastructure project in Python 3) but am tired of imperative

sundarj01:06:22

i also converted to Clojure from JavaScript for the same reasons 🙂

kyselyradek01:06:28

Right. I mean Node.js is still a great prototyping platform for me, but for long time I’ve been telling myself “there must be a better way” and Rich Hickey seems to address all of those issues in his talks (using Clojure) 🙂

sundarj01:06:51

note that you can use ClojureScript on Node.js if you like

kyselyradek01:06:04

That was also one of the Clojure’s selling points to me 🙂

sundarj02:06:06

$ clojure -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.10.238"}}}' -m cljs.repl.node
ClojureScript 1.10.238
cljs.user=> (clojure.string/upper-case (-> (js/require "fs") (.readFileSync "foo.txt") (.toString)))
"HELLO WORLD\n"
cljs.user=> (map {"H" "H", "E" "O", "L" "L", "O" "O", " " "W ", "W" "W", "R" "R", "D" "D", "\n" "\n"} *1)         
("H" "O" "L" "L" "O" "W " "W" "O" "R" "L" "D" "\n")
cljs.user=> (apply str *1)
"HOLLOW WORLD\n"
cljs.user=> (-> *1 (clojure.string/lower-case) (str "!!"))
"hollow world\n!!"
:~)

kyselyradek01:06:01

However I learn the best by actually making useful stuff—unfortunately, most of the Clojure guides seem to focus on trivial algorithms, not on building in practice

kyselyradek01:06:17

Do you know any resources that focus mainly on building Clojure knowledge by actually making some small but useful apps? Preferably (but not necessary) video format?

sundarj01:06:04

Programming Clojure and Clojure Applied @radekdymacz

sundarj01:06:18

i started by watching Simple Made Easy and some stuff on YouTube like https://www.youtube.com/watch?v=VVd4ow-ZcX0

kyselyradek01:06:19

Hey, thanks! 🙂 I literally am just watching Simple Made Easy 🙂

kyselyradek01:06:02

I’m basically looking for something that’ll get me be somewhat comfortable in the language and project setting and from there I’ll be able to go on my own

sundarj01:06:02

those two books i recommended should do that, lots of people also start with Web Development with Clojure

sundarj01:06:02

the only video course i'm aware of is https://purelyfunctional.tv

jumar06:06:39

@radekdymacz there's also https://lambdaisland.com/episodes which is cheaper but http://PurelyFunctional.tv is much more curated and more complete I'd say.

jumar06:06:56

Tim Baldridge also has some Clojure Tutorials: https://tbaldridge.pivotshare.com/ Again, perhaps not that interesting for a complete beginner. But good for learning particular aspects of language (transducers, core.async, etc.)

kyselyradek01:06:53

I am checking out those books and yeah, they seem to promise what I’m looking for

kyselyradek01:06:09

I also signed up for PurelyFunctional but that doesn’t seem beginner-friendly to me

kyselyradek01:06:40

It’s a library of videos, but I’d prefer already sorted curriculum

kyselyradek01:06:38

I think I’ll check out those books, thank you @sundarj 🙂 🙂

sundarj01:06:46

@kyselyradek does https://purelyfunctional.tv/learning-paths/ match what you were looking for?

seancorfield01:06:27

(I haven't read it but a lot of people seem to follow that book's path to get started writing web apps in Clojure)

kyselyradek01:06:02

Yeah, Sundar also mentioned it but I’m not really interested in web development

kyselyradek01:06:13

So I’m not sure whether it’d be the best fit for me

seancorfield01:06:00

My path was... unusual... I did Lisp at university in the early 80's and then a PhD in Functional Programming Language Design & Implementation... so when I started Clojure, it was more of a "home coming", and I started with The Joy Of Clojure. It's more of the "why" of Clojure rather than a beginner's book, but I think it makes a really good second or third book...

seancorfield01:06:44

What sort of apps are you interested in building?

kyselyradek01:06:37

Actually, now that I’m looking at the table of contents, it seems kinda helpful, you use web-based protocols for multiple applications

kyselyradek01:06:46

I’m mostly working on standalone server apps

seancorfield01:06:06

I'm purely a server-side dev too.

seancorfield01:06:43

I build REST APIs and occasionally a small app with actual HTML web pages but mostly backend process stuff.

sundarj01:06:00

i second Joy of Clojure for a follow-up book, it's excellent

kyselyradek01:06:17

Okay, thank you guys for your recommendations, I will check those out 🙂 Sundar, I didn’t know about that “Learning Paths” playlist, I just can’t navigate that website 😂

sundarj01:06:55

haha, fair enough

jhilan.ai04:06:01

Hello everyone, question about managing state.. building small SPA where client initate websocket then server Pull data from some apis and push to the client on schedule, wanted to make the API querying happen only if theres clients connecred so found my self keeping track of ws connections count in an atom int and Inc and dec that count whenever new ws open/close, my question is Is there other technique or idiom to deal with this kind if state with less mutating functions or it's normal use case to store state in global atom in namespace and let functions mutate it

donaldball04:06:36

That is fairly common, though folk would advise to write mostly pure functions, and keep the impure functions that mutate the system atom to a small handful of top-level action functions. If your state gets big enough to become confusing, there are libraries that offer various approaches to managing it. Depending on your server impl, you could keep your system atom in a closure instead of a var if you like.

donaldball04:06:52

Oh, and it’s possibly neither here nor there but you may find using a set instead of a counter more useful, if your websocket thingies have a stable identifier

jhilan.ai04:06:34

Thank! keeping atom in closure is an option I have not tried before , will look into what library available, definitely felt with more complexity there must be more functional way or way of re-org things to better manage states

donaldball04:06:11

Component, immutant, and mount are the three libs people talk about the most, but I wouldn’t reach for them before you need to except for inspiration or comparison

me167304:06:08

Hello everyone, I am Gavin! I am new to Clojure. I am working on a simple Clojure 1.9 project. It was configured with a minimal .travis.yml to build on Travis. The build failed on OpenJDK 9 but not other JDK environments. Anybody knows why? Thanks!

seancorfield05:06:27

@me1673 Java 9 brings some challenges for Clojure -- which have mostly (all?) been addressed in recent updates to Leiningen/Boot etc. So it may be that you're not getting a recent enough Leiningen version on Travis?

seancorfield05:06:58

Hmm, security stuff...

me167305:06:15

@seancorfield Thanks for your help! I have tried Leiningen 2.8.1 but no luck. Moreover, it works with OracleJDK 9.

seancorfield05:06:36

Am I reading your project right that it's all the Clojars artifacts that won't download -- but the Maven artifacts download just fine?

me167305:06:06

Yep, it is the case! Didn't notice myself. Maven Central artifacts were fine.

seancorfield05:06:51

Hmm, so that suggests a certificate issue between OpenJDK9 and Clojars -- maybe go ask in #clojars and see if anyone there knows what might be up.

seancorfield05:06:25

Sorry I don't know which side might need changes there. JDK/certs stuff is a black art 👀

vincent.cantin05:06:49

I moved my transducer exercises to Github and added all the solutions too. https://github.com/green-coder/transducer-exercises

me167305:06:44

@seancorfield I see, thanks Sean! 😂

me167305:06:53

Big thanks for your help! Have a great day!

lady3janepl08:06:56

Yay! https://github.com/ir-regular/okasaki-clj Thank you for help and ideas @noisesmith @dpsutton @yogidevbear 🙂

denisgrebennicov15:06:20

Currently my function makes 2 rest calls, since the last expression is a rest call, this is exactly hat is returned. I want my function to return nil, what should I do? Add nil as the next expression? That seems kinda ugly Example:

(defn my-fn []
  (rest/x)
  (rest/y))

seancorfield15:06:39

@denisgrebennicov Why do you need the function to return nil? If the function is just doing side-effects (calling REST APIs) then it's caller can just ignore whatever it returns...

denisgrebennicov15:06:41

@seancorfield it doesn’t necessarily have to return nil, but I was thinking that this might be good idea.

seancorfield15:06:57

Unless you're specifically returning a value that you expect the caller to care about, I wouldn't worry much.

seancorfield15:06:26

I guess I might, at that point, be more concerned that you're just throwing away the results of the REST calls 🙂

denisgrebennicov15:06:49

It was just irritating to see all the REST call result, when calling the method in the repl

seancorfield15:06:43

@denisgrebennicov Fair enough. I'm not sure what the idiomatic approach would be there. I guess (when (my-fn)) is as good as anything...

seancorfield15:06:34

If you're getting back some sort of collection or sequence, dorun is probably more idiomatic? (dorun (my-fn)) but it expects to be able to call seq on that expression and next to walk all the way through it.

lee.justin.m15:06:20

I kind of wish the do verb had been reserved for “side effects only” instead of “unlazify”

seancorfield15:06:31

@lee.justin.m how would you write the equivalent of (do (f1) (f2) expr) then, where you wanted some side-effects and then a value returned?

lee.justin.m15:06:13

I haven’t really thought this through, but generally I think the highest utility in naming a function is explaining what it returns / how to use it. I.e., it’s nice to be able to distinguish between a side-effect-only function, a function that returns a lazy result, and in cljs, a function that returns a promise or a channel. It’s probably not possible to encode too much, but, still, at least with laziness I regret that there’s not a convention for “this is lazy” instead of using do for “this makes thing unlazy”

lee.justin.m15:06:50

so for a function that side-effects and returns a value, i’m not sure i’d name it anything in particular at all

lee.justin.m15:06:26

also incase i wasn’t clear, i don’t mean the special form do, i mean using do in function names

lee.justin.m15:06:46

like doall and doseq

seancorfield15:06:46

I figured, but wanted to check. I think of do... as an imperative, rather than anything to do with laziness.

denisgrebennicov15:06:48

I’m working with spec right now. Want to catch/parse the value which ends with * (for my regex parser) Currently something like this doesn’t work -> (s/cat :base ::base :star #"\*") Any ideas? The regex matchign doesn’t seem to work and I don’t know how to make string match in spec.

sova16:06:47

@denisgrebennicov you need to escape an asterisk.. maybe \\* ?

seancorfield16:06:44

@denisgrebennicov spec is not designed for parsing strings.

alexmiller18:06:34

you can use re-seq to make a predicate matching a string regex

kyselyradek19:06:24

EU residents: Where did you get your Clojure books from? (mostly Programming Clojure and Clojure Applied) They’re either out of stock in EU or have insane delivery fees from U.S. (like higher than the book price)

alexmiller19:06:00

I’ve so far heard mostly this wrt obtaining these in the EU unfortunately

scott.archer19:06:30

Digital for the win?

alexmiller19:06:57

I’ll ping my prag contacts and see if they have any advice

kyselyradek19:06:50

Digital ones are delivered as PDFs?

scott.archer19:06:28

The Pragmatic ones are DRM Free in epub, mobi, and PDF format. (You get all three) It's really nice. I can use iBooks, Kindle, or something else.

kyselyradek19:06:11

So if it is iBooks, then good

kyselyradek19:06:27

But I’ve had PDF books in past and the experience was sub-optimal

sova19:06:06

e-ink kindle?

sova19:06:16

because those are very kind on the eyes

kyselyradek19:06:54

Don’t have Kindle device 😂

blance20:06:02

is there a good rule of thumb or example regarding how to separate out pure function vs side-effect functions? The concept of pushing them to the edge is nice, but in practice, the codebase gets contaminated pretty quickly, and i have to constantly think if I need to add a doall before each pmap because the actual side effect function could be many level below the immediate pmap'd function. This also results in most of the tests needing to do with-redefs etc.. It's hard to describe but it kind of feels like writing Java in clojure.. It would be really helpful if there's a medium to large scale code base that deals with DB access and HTTP requests in a clojure way.

seancorfield20:06:00

Re: ebooks -- I pretty much only ever buy PDF versions of books these days. I keep them all on OneDrive and read them on my iPhone, my laptop, and my desktop. Guess I've gotten used to it, but I buy a lot of pre-released versions and it's nice to see how they evolve into the finished product. I think I have about 100-150 tech books on my iPhone now...

sundarj21:06:06

why have i never thought of this, that is so much easier than manually copying books from computer to phone. you're a genius

seancorfield20:06:02

@blance pmap seems attractive but it's a bit of a sledgehammer (and it sounds like you're already starting to experience some of the downsides?).

seancorfield20:06:45

It's hard to completely separate out pure and non-pure functions in real world apps but, as much as possible, try to do all of the "queries" you need upfront, pass all that data into your pure functions, and have them return a set of changes that need to be persisted externally.

seancorfield20:06:43

That can get pretty complicated -- so you have to compromise to keep your code readable sometimes.

seancorfield20:06:15

Something that you can do is define protocols for the APIs of the external systems you need to interact with and then you can easily write a stub implementation for testing. Stuart Sierra talks about that with his Component library. I think he did a talk called Clojure In The Large which covered that sort of thing.

seancorfield20:06:25

An earlier version of the talk from a user group in NYC https://vimeo.com/46163090

blance20:06:56

I'll give it a watch, Thanks!

blance20:06:16

I've been working with clojure in production for 1.5 years, but the code base I'm working on still feels not right.. side effects are everywhere, I can hardly write any test without stub. like I mentioned earlier, it just feel like writing java in clojure syntax.

blance20:06:31

yet I can't find a good starting point to refactor:disappointed:

blance20:06:55

i'm pretty new to software industry in general (2 yr out of college)

seancorfield20:06:10

Yeah, our early Clojure code was a bit of a mess -- stateful singletons and other horrors. Still slowly digging our way out from under parts of that.

hiredman21:06:06

some good general advice is "never use pmap"

hiredman21:06:56

I mean, be liberal in what you accept, and conservative in what you do, or however it goes

hiredman21:06:25

but pmap is a seductive abstraction that is full of holes

alexmiller21:06:54

I would generally agree (but do find it useful as a quick solve for some chunky problems)

hiredman21:06:42

with-redefs in tests is also a bit of a red flag, in a well put together system you should be able to pass in a different argument for whatever to get a different behavior. you have to be careful combining with-redefs and any kind of concurrency

blance21:06:08

if I remove all tests that needs with-redefs, I would only have 5%~10% left:joy: I get the concept for the most part, but sometime passing as arguments would mean it would be passed around all over the place, and i'm not sure if it's a good practice to let intermediate level knows all the details that only upstream/downstream cares For example, if I have a call chain A->B->C->D, where D needs some data from external source. would I fetch it in D, or fetch it in A and pass all the way down?

seancorfield23:06:50

If you abstract "external source" into a protocol and an implementation, and pass it (as part of a "system" map) through that call chain, then D would call a method on that source to get the data. That would at least allow you to create a mock implementation of that protocol and pass it in for testing -- so you could control what D got from the source.

seancorfield23:06:34

But, as others have indicated, if you can do the "external source" stuff at the top and pass it into the whole chain, your life would likely be less painful.

seancorfield23:06:42

If you start to practice that, then D would never get written to pull data from an "external source" in the first place, since D would expect to be passed its data as an argument.

seancorfield23:06:24

(and, yes, this can be really hard in programs that interact with databases -- unless that database is Datomic and you can treat it as an immutable value 🙂 )

seancorfield23:06:51

We certainly have plenty of stuff at work where call chains like this exist and D reads from (and sometimes writes to!) the MySQL database. We regret that approach now.

blance00:06:12

Thanks for the detailed write up!

blance00:06:39

Would you say this pass from top approach works with mutable variables as well?

blance00:06:48

Also similarly I assume for writing to external resource, D would return the data all the way back to A and A then calls E to write out?

blance00:06:28

But wouldn't that violates DRY? if A' also wants to write, then it needs to get from D then Calls E as well

seancorfield01:06:19

Like several have said, separate all external reads and all external writes and keeping them out of your pure code and just doing them at "the top" is hard. Possible, but hard. Often it requires completely rethinking how you would approach a problem. There's no One True Way and you'll generally have to make some trade offs.

seancorfield01:06:13

I'm not sure what you mean about passing mutable variables in from the top -- mutable variables are extremely rare in Clojure.

seancorfield01:06:33

One problem you can run into with trying to completely separate out reads, processing, and writes can be seen in a library I wrote and then sunsetted after I'd tried using it in production code: https://github.com/seancorfield/engine

seancorfield01:06:36

The resulting code can look very "monadic" which is not at all idiomatic Clojure:

(-> app
    ;; indicate desired updates
    (e/update :user {:id 9 :username "nine"})
    (e/update :user {:id 10 :username "ten"})
    (e/delete :user 11)
    (e/update :ram :name "Sean Corfield")
    ;; indicate intended result
    (e/return 42)
    ;; commit changes
    (e/commit!))
;; returns the result and applies the updates
and this indicates that the way of thinking about the problem is incorrect.

seancorfield01:06:59

Part of the problem there is lower-level code trying to describe the shape of the changes that are needed to external systems -- you end up with a domain specific language and an interpreter, which is much more complicated than the problem you started out trying to solve. Sometimes the solution can be to break the problem into smaller pieces and then combine those in a pipeline (so the reads/writes are at the top-level of the pipeline and all the pure processing is done in between). Sometimes you can leverage core.async as a way to "produce" data/changes from code, where there is a separate consumer that does the writes -- passing the "output channel" into the otherwise pure functions (and then, for testing, you write a mock consumer that can verify the expected data ended up on the channel).

seancorfield01:06:40

For example, we have a sitemap generator process. It reads data from MySQL, processes it, and sends the processed data to various channels (that are created up front and passed in), then there are consumers on those channels that write the processed data out as XML. For testing, we can provide "canned" data instead of a database and we can consume the channel data and verify it is appropriate.

seancorfield01:06:17

(our actual process is more complex and can't quite be tested that way -- but that's mostly because I didn't spend a bit more time thinking about it before I wrote the code!)

seancorfield01:06:00

Hope that helps?

blance02:06:27

This is very helpful, especially the channel part. I think I'll need some time to process these and try to refactor part of our code base to see it in action. Thanks a lot for these!

seancorfield03:06:19

I wish I'd gotten into core.async long ago. It's hard to get your head around, but it really can help decouple things.

mshanmuga12:06:43

if possible can you give us some sample code describing the solution for the above mentioned code with or without async? Thanks

seancorfield01:06:36

@ The only code I have that really shows this stuff off is work-related, so I can't share it I'm afraid. Sorry.

alexmiller22:06:04

there is no simple answer to that question

alexmiller22:06:31

but in general I would try to separate <code that talks to external stuff> as much as possible from <code that uses data>

noisesmith22:06:23

and having anything stateful / side effecting / talking to the outside world deep in the call structure is a code smell in Clojure - everything gets a lot simpler if i/o and state is as close to the top of the call-chain as possible

noisesmith22:06:45

so then the only things getting passed deep are immutable values, which are much less dangerous to share

fappy22:06:35

Hello 🙂 If I do the following, I'll end up with a symbol. What can I add to get a name instead, so that I can call clojure.repl/doc on it? (My goal is to get all the fns in a namespace that have a particular signature. It's possible there is a better way entirely...)

(->> some.namespace
     clojure.repl/dir
     with-out-str
     clojure.string/split-lines
     first
     read-string)

fappy22:06:03

my use of first is just for testing

alexmiller22:06:16

Look at the ns- functions to start with data

fappy22:06:22

What code can get the signature of something from say ns-publics?

sundarj22:06:42

@fappy

=> (-> (ns-publics 'clojure.core) (get 'map) meta :arglists)
([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])

fappy23:06:57

@sundarj thanks!