What do beginners struggle with?

A word about me. A seizure has forced me to use a phone as my left hand is useless. Serious frustration with files using Android 10, no root authority. Want to write simple prog to find strings in local files. Two problems, don’t have r/w access to a file I created. And for the life of me I can’t find doc to open, read and write files.

Regarding the first, I plugged “file:///storage/emulated/0/a.html” into Chrome and got:
ERR_ACCESS_DENIED. Also tried “http://127.0.0.1:27524/storage/emulated/0/a.html” and got:
This site can’t be reached127.0.0.1

refused to connect.
Try:

Checking the connection
ERR_CONNECTION_REFUSED

I don’t know how I found “http://127.0.0.1:27524” and since I failed to copy it, I got to waste hours looking for it. Am seriously disappointed with my 2 apps, especially the pro version of one. All they give is (slurp “a.txt”) and (spit str “a.txt”). Seriously.

clojure/clojurescript.org don’t give simple examples either. What additional software do I need? Not looking forward to installing JDK and a ton of additional software given my limitations. Just really like what I see of Clojure and would like to learn as much as I can.

TIA

The good news is that we now have auto complete thanks to clojure-lsp
Here is a short talk about it from @borkdude :

Hey, that’s true, we do have a few options for static auto-complete, Cursive has also been doing it for a few years now, and more recently with clj-kondo you have clojure-lsp and anakondo doing it as well. We also had auto-complete with a connected REPL for a while thanks to complement.

That said, we do not have auto-complete that shows only operations available to the type. And that’s the bit I was referring too. People used to Java for example with IntelliJ, they often don’t learn to know what types things are and what operations the types have, they simply rely on their IDE for it. It’s a little how you forget even simple multiplication like 9*4 once you start using a calculator.

1 Like

People used to Java for example with IntelliJ, they often don’t learn to know what types things are and what operations the types have, they simply rely on their IDE for it.

I find it the opposite. Auto-complete accompanied by documentation and navigation features helps me get a better overview of the libraries I’m using.

1 Like

Me. All the time! :smiley:

2 Likes

I don’t think I’m a beginner, I’ve been playing with Clojure on and off for about 8 years, but I’ve started a new project recently with clojure cli / deps.edn. The biggest problems I’ve had are:

  • Identifying Clojure application architecture best practices
  • Tooling ergonomics
  • Library discovery and selection

For application architecture, Eric Normand’s question “Organizing Clojure code - A real problem?” has been a great help. Turns out I was bringing some C# habits to Clojure.

For deps.edn, examples have been really useful, if a little overwhelming:

I find deps.edn to be a pretty nice way to encode build related stuff as data, and I find it easier to understand than Leiningen, but the ergonomics are just ghastly. I know we should favour simple over easy, but I don’t think it’s unreasonable to want to have both. Tools like Git or Homebrew have a relatively friendly and discoverable interface that helps you learn more about how to use the tooling. I understand that clojure cli is supposed to be simple, and that these kinds of ergonomics aren’t part of what it does. Unfortunately, as a byproduct, I spend far too long trawling through examples and guides to try and curate my own set of aliases.

I found the post “[Idea] A tool for helping developers get started using Clojure CLI tools and deps.edn - Community Center / Community Building - ClojureVerse” which led to the creation of “lilactown/plum”, but at a glance that doesn’t seem to have gone anywhere.

I found an example of a makefile wrapping deps.edn in antq and ended up learning how to debug make so that I could have one too. A couple of days later borkdude announced Babashka task runner, which is rather nice, and I converted my project to using that.

My project is a small thing, and it’s an attempt to port an overly complicated project that I built with Leiningen over to deps.edn. Part of that complexity was too much state in too many places so I decided to find some libraries to handle sql queries, connections, and migrations. There are quite a lot of libraries out there, many of them abandoned (though that doesn’t always matter). Finding them and choosing between them is a time consuming nightmare. Not to mention library gotchas like logging and handling of date and times.

I’m now at the point where I’ve got a bug relating to how migrations are running, which is fine, and I thought I’ve quickly whip up test project so I can try an isolate the problem. I couldn’t remember the semantics for calling seancorfield/clj-new, so had to open up my .clojure/deps.edn to check. I thought about wrapping my global deps.edn in Babashka tasks to try and provide some executable documentation, but it feels like I’m just pushing deps up hill at this point.

So, here I am, frustrated, having poured time into a hobby project where the only real headway I’ve made is setting up tooling, and I’m still haunted by the thought that the next time I set up a project it’s not going to be much easier.

I don’t think the clojure community is deliberately making things hard for beginners, but it is hard. Simple is wonderful, I love simple, but I don’t think it’s unreasonable to want to have at least some of that simple within reach, for it to be easy.

If I’d done this project at work, in C#, I’d already be done. That’s because it’s my day job, but that’s also because all the pieces I need to make it work are within easy reach.

Don’t get me wrong, when I’m dealing with the internals of the application, when all the libraries are in place and I don’t have to think about how to get a repl running, Clojure is wonderful, and I often solve C# problems first in Clojure and try and convert the solution. But getting to that point seems a lot more painful than it has to be.

4 Likes

tools.deps is NOT a build tool. It is not meant to encode build related stuff as data, but only launching apps with appropriate dependencies pulled in and made available. It is a dependency manager and application launcher for Clojure.

With this in mind, it makes sense that it doesn’t offer the ergonomics you’d want of a build tool. The fact it’s so simple has enabled others to very easily and quickly (I kid not, like in one day you can build one), create full build tools and build pipelines around it.

So what’s happening now is there is no “winning” build tool in the community yet. Leiningen used to be it, but since tools.deps has made it so easy for everyone to write their own build tool, it created a state where each project has their own build machinery leveraging tools.deps.

So if you followed, you’d now understand this is totally fine, you do need to add some build tooling to your project, because tools.deps does not provide any build facilities of its own. So wrapping/extending it with something else to allow for defining build tasks and build task pipelines is necessary.

Alright, now, the core team is currently working on a tools.build to complement tools.deps as the official build tool, but not much is known of it yet. In the meantime I think babashka task is a great start putting together your own build tooling.

1 Like

I hadn’t heard about tools.build, that’s something to look forward to. Thanks.

Hey there, I’m a beginner, and here’s what I’ve found difficult so far:

  1. Refactoring. When I need to change things I’m a lot slower. I love emacs, but the ramp up time to get anywhere near as quick as an Intellij product is still large ahead of me. When I want to move things and extract things it just takes a bit of time (in emacs-for me-as a beginner). Also, I wish there was auto-importing/requiring when I specify a fully qualified variable (is there?).

  2. Working with async data (network resposnes). When I fetch some data asynchronously, how can I use that data throughout my application without a network call each time I need that piece of data? ie. auth token.

  3. Sometimes I wish the documentation was better. For instance when I first started splitting out my functions from a single file, I was trying to learn how use namespaces so I read Clojure - Namespaces and there’s no examples… just a programmatic explanation of what it is. I learned how to use namespaces and how/when to use :require and :use by googling clojure projects github and looking through other peoples’ source code.

So far, other than that, I love everything about Clojure though and it’s what I want to make my main language (I use Kotlin and JS at work).

I’m currently using it to build a Postman-like helper tool for myself to generate and move data for me in the services I’m working on across their many different environments and for this I absolutely love the repl. I can build functions in clojure around services’ apis and create complex automation tasks that a typical rest-client can’t do and I can run those functions while simultaneously building them, it’s absolutely insane I love it. I want to do something, I write the function, I evoke it. Love.

Please upvote this issue: Namespace introduction/guide is needed · Issue #419 · clojure/clojure-site (github.com)!

2 Likes

Is there any reason you don’t just stick with Intellij and Cursive?

I’ve been using Clojure professionally for 3 years and I still don’t quite get the hype for emacs when it comes to Clojure development. I mean, I get the whole philosophy behind emacs and the synergy of the Lisp heritage, but from what I’ve seen of various Clojure emacs setups they rarely beat the functionality of (a barely configured) Cursive. I’m sure some 10xers with finetuned setups will disagree with me, but most people don’t operate at that level.

This is just going to be an echo of what @simongray wrote above. :slight_smile:

Over the years I have been coding Clojure in vim, emacs (spacemacs and doom), atom, and for the last couple of years Cursive. My day-job these days being Java/Typescript/SQL, I wanted an environment that could handle it all as well as Clojure for fun-time, and to be honest, I think Cursive beats the others for ease of setup and comfort.

I enjoy using it for how lightweight it is compared to intellij. When I’m working I have typically 2-5 intellij applications running and a couple servers, I really don’t want to add another one for clojure (clojure is not part of my tech stack at work-just using it to build personal tooling). That and I also like having the same setup on my personal computer since I don’t have my own license of intellij.

That being said, as my application grows I most like will give cursive a shot!

Not sure what you mean by “async” and why would you need to call it again every time. You can use (future) and (promise) and other mechanisms. I like the Imperative Shell, Functional Core architecture - first I fetch all the data I need then pass it as data to the pure functions doing the actual work, finally perform any effects they return.

The https://clojure.org/reference/* resources are not meant for learning. Many people like Clojure for the Brave and True. Have a look at its Organizing Your Project: A Librarian’s Tale | Clojure for the Brave and True

Not familiar with that architecture! I will look into promise and future.

For async I mean this: I have a server (A) that logs into other servers (B) and retrieves an access token. Then I call (A) to perform a series of api calls on (B), but those calls need that access token I retrieved earlier from login. I’m using an atom to store the access token, but it’s not clear to me how to use pure functional programming to ‘store’ the access token I retrieved from a network request (async) and then how to use that access token for other async calls triggered by a client.

Hope that makes more sense

That’s a confusing use of the term async, but I understand what you mean, except it seems all your calls to A and B are actually synchronous in this case, or at least that’s what I gather?

In any case, it doesn’t seem to be the issue is with async, but just managing state. For managing state, an atom is a good choice. In your case, you want to cache the auth-token, and atoms are great for that, you could also look into memoize and company. Technically you can also leverage closures, deftype or recursion, but those are more advanced and honestly I wouldn’t say they are better, atom is what you want in my opinion.

Yes there is. You want to use clj-refactor Home · clojure-emacs/clj-refactor.el Wiki · GitHub

Once you have that, there’s a feature called Magic requires, it’ll automatically add the require for common alias, and if you’ve had the alias for it already in another namespace, it’ll pick that up as well, and you can add your owns to cljr-magic-require-namespaces

So basically if you type str/ the require for clojure.string aliased to str will be added automatically and the ns declaration will get re-evaluated as well so you can start using it immediately at the REPL.

Finally, the one you probably want the most is add-missing-libspec see here: cljr add missing libspec · clojure-emacs/clj-refactor.el Wiki · GitHub

There’s other refactorings available in it as well that can be handy.

Emacs is a bit of a beast, but can be quite productive for Clojure once you figure it out. I recommend a combo of packages for Clojure:

clojure-mode + Cider + clj-refactor + smartparens + aggressive-indent-mode + adjust-parens + flycheck-clj-kondo

Clojure-mode does syntax highlighting, indentation, and some basic refactorings. Cider gives you integrated REPL, debugger, documentation, auto-complete, code navigation and code browsing, clj-refactor adds more refactorings and what I mentioned before, smartparens does bracket management and structural code edits, aggressive-indent-mode keeps everything auto-indented and formatted all the time, adjust parens lets you wrap/unwrap code using tab, and flycheck-clj-kondo gives you very powerful real-time linting.

Setting this all up is a bit tricky, but that’s part of learning Emacs. I also use Spacemacs and have other customizations that are not Clojure specific per-say but add a lot of features to Emacs like a project panel, smart jumps in the code, git integration, command auto-completion, etc.

3 Likes

Wow thank you for this, I will look into those emacs packages.

And yeah I guess I was using the term ‘async’ incorrectly. What I mean was just making ‘network’ calls? Anyway, I’m curious to know how you’re supposed to manage network data as state in other fp languages to that don’t have atoms. You mentioned closures/deftypes/recursion but I don’t have the knowledge to know how to apply those to this situation, but it is something to keep in my awareness, thank you!

They’ll most likely all have a construct similar to an atom, for example Haskell has TVar, MVar, TMVar which are similar managed safe mutable containers like Atom and Ref are in Clojure. This is because state is often unavoidable, and memory is not infinite, so at some point you need mutation, but what FP languages do is isolate that mutation and manage it so you limit its impact on program complexity and safety.

Now probably what you want to hear as you’re trying to keep things purely functional and I feel your question is… but aren’t these mutable state containers non functional?

Well the answer is to keep to a pure FP style, you need to model things as a data-flow and pass state along. You maintain state because it’s constantly passed along to the next thing running or waiting. Sometimes that’s more ackward than good though, and lots of frameworks in FP languages won’t do that, they’ll use a managed mutable container like an atom.

So here’s the gist:

waitForNextCommand(state[])
-- command, state -->
if state missing token
  -- state[] -->
  getTokenFromA
  -- state[token] -->
  callB
  -- state[token] -->
  waitForNextCommand(state[token]) ;; recursive
else if state has token
  -- state[token] -->
  callB
  -- state[token] -->
  waitForNextCommand(state[token]) ;; recursive

Now there is no global state, and no mutable containers, only functions of immutable input to output calling each other in a data-flow which is recursive, yet you won’t call getTokenFromA if you already have a token.

That said, waitForNextCommand won’t be pure, and that’s normal, at the boundary something has to be impure as it interacts with the user or external world.

2 Likes

Thank you, a clear and precise explanation. If you want to copy/paste that answer to my q on stack overflow, I’ll accept it. I haven’t seen anywhere an explanation that made sense to me yet.