beginners

Noah McMillan 2025-09-22T10:57:30.476729Z

As a hobby programmer, I am thinking of building a desktop app or something that runs locally (to avoid hosting and server maintenance). I want to be able to share the app with friends as a standalone program. I have looked at humble UI. I have also tried with just a static website (using reframe), but felt a little hacky (especially with persisting data on local storage) I am wondering if people have advice on where I might look? I feel this would reduce a lot of complexity and allow me to spend more time on my applications. Building apps that run fully local feels pretty uncommon (most defaulting to the cloud) and challenging to do. Any thoughts on why? I have seen a few talks on local-first, which do interest me. More context: I have built a few web applications in clojure/clojurescript. For me, I feel like there was a lot of extra time figuring out hosting and maintenance. Then there is a cost of having it hosted. Being able to use something similar to datomic for storage and re-frame or replication for UI would be great.

gaverhae 2025-09-24T12:39:29.868739Z

It took me an embarrassingly long time to stumble upon it, so I'll mention that jpackage is a thing. It makes it practical to build a desktop app in Clojure and give out runnable packages to your colleagues / users, so they don't need to install a JVM and figure out how to run JARs.

gaverhae 2025-09-24T12:41:18.474589Z

Being a local, desktop app it can use Swing (possibly with Seesaw) for the UI, and can write files directly to the hard drive, possibly using https://blog.datomic.com/2023/08/datomic-local-is-released.html if you like that data model but don't want to figure out how to host stuff.

gaverhae 2025-09-24T12:42:31.184299Z

It doesn't solve everything, and in particular app upgrades can be substantially more tricky, but I think overall a desktop app is a lot less complex than a web app.

Noah McMillan 2025-09-23T08:00:06.931299Z

@bhlieberman93 yes, that is a good point. As a solo developer you need to do it all, which is totally what you sign up for

gaverhae 2025-09-26T08:21:34.228619Z

I don't know how far it will get you, but there is a https://docs.oracle.com/javase/8/javafx/api/javafx/scene/web/WebView.html. I've never used it myself; I would expect it to be significantly slower than "standalone" web browsers, and to not implement all of the advanced / recent CSS features, but for fairly basic layouts it might work well enough.

👍 1
Roman Liutikov 2025-09-26T09:20:55.926569Z

@noahmjohnstone no idea, I wouldn’t brother with external storages, there’s indexed db already, it works fine

👍 1
Noah McMillan 2025-09-26T01:05:00.249339Z

@gaverhae nice will look into jpackage, and I think datomic local is a good idea. @roman01la do you think I could use Tauri with datomic local. Would the rust backend be able to spin up a clojure process. I think doing ui with web stuff make sense as I know some stuff already.

Roman Liutikov 2025-09-22T11:03:29.708689Z

Electron or Tauri is a good choice if you are into web UIs development. Usually you'd persist data in IndexedDB. that's how https://pitch.com/ is built (clojure/script and electron)

1
daveliepmann 2025-09-22T11:11:57.324729Z

Consider https://github.com/clj-commons/seesaw

➕ 1
1
Ed 2025-09-22T11:55:30.808569Z

There's also https://github.com/cljfx/cljfx which I think is used to build https://vlaaad.github.io/reveal/

1
liebs 2025-09-22T12:40:07.572419Z

You can get pretty far with static websites these days, just some HTML and a local web server like Nginx or Apache. I don't really know what the tools for making static sites are like in Clojure though.

1
liebs 2025-09-22T12:42:13.624529Z

(I am also a hobbyist (though erstwhile professional) programmer and if there's anything Clojure has taught me, it's to go for absolutely the simplest tool you can find to do what you need/want to do)

Noah McMillan 2025-09-22T13:11:38.557359Z

Yes agreed, I have done some static sites with re-frame. It seems like the browser is the new runtime. I imagine there was a time where everyone was just making desktop apps. I think electron would suit because it means I can use my current knowledge but the application ships with a copy of chromium which seem complex. I like the model of emacs packages or vs code extensions. You write something useful, and people can run it locally without needing to host it or build anything from scratch. Obviously, I want more ui capability. I am assuming datascript is fine with electron, and I could still call to a api backend if needed.

Noah McMillan 2025-09-22T13:14:30.707849Z

Will defintely check out tauri!

practicalli-johnny 2025-09-22T13:40:21.295589Z

Deploying a ClojureScript app to GitHub pages and managing data via Firebase can greatly reduce complexity of web deployment. Cost would be free unless more data is required than the Firebase free tier offers.

👍 1
Roman Liutikov 2025-09-22T13:43:21.442699Z

for deployment: I'm using Cloudflare these days, really easy to use and it's free, also includes more stuff if you need it later

👍 1
daveliepmann 2025-09-22T14:00:28.666829Z

> to avoid hosting and server maintenance Another option might be https://github.clerk.garden/ if your app fits in its specific, opinionated parameters

👍 1
James Amberger 2025-09-22T14:13:00.708649Z

> Any thoughts on why? Interesting question imo. I attribute it to historical lock-in arising from 1) the failure of legacy mass-market OSes to provide stable, secure ways of installing and upgrading code while also protecting proprietary interests and 2) the very very low limits of where normie users will meet you. Getting them to type a url was good enough for a long time; now they don’t even want to do that (and forget about “bookmarking” one).

👍 1
Ludger Solbach 2025-09-22T17:12:10.424719Z

With a web app, you're in control of the deployment and you only have to do it once. With a desktop app, the user has to do the deployment and you're not in control. There are lots of things that can go wrong when users deploy your software on their systems.

👍 1
2025-09-22T22:58:55.853159Z

Seconding the Seesaw recommendation. Java Swing is so much easier in Clojure than it is in Java... similar to JDBC but even more so. Seesaw is a very nice wrapper for it. IIRC Seesaw also wraps Miglayout, which is a marvelously declarative 3rd-party swing Layout that sort of brought to Swing the wonders and convenience of CSS gridlayout about a hundred years before CSS gridlayout was invented.

Noah McMillan 2025-09-23T04:02:42.582409Z

Sorry for long message. Just documenting my random thoughts. I have had a bit of a think about why I asked this question, and I think I am trying to answer and solve this question: Why am I so unproductive as a hobby programmer? I’m a teacher who wants to build tools for other teachers. I’ve made a few throwaway apps (e.g., http://learningnow.com.au) and now I’m working on a tool to parse dense curriculum documents. It could be simple: a desktop app that outputs text. But as a hobby programmer, I quickly run out of two scarce resources: time and knowledge. I spend more energy figuring out how to build an app than actually solving my problem. Here are why I think this is the case. Complexity in modern development Modern web development is complex. Peter van Hardenberg (https://www.youtube.com/watch?v=0bjAI57_cDk) shows how even simple apps require layers of setup. He also suggests ways to reduce complexity—fewer dependencies, simpler architecture (https://www.youtube.com/watch?v=czzAVuVz7u4&t). Jonathan Blow (https://www.youtube.com/watch?v=ZSRHeXYDLko&t) also argues that programmer productivity has declined because of ever-growing complexity. Getting lost in side quests Instead of solving my main problem, I often drift into side quests. Rich Hickey (https://www.youtube.com/shorts/MVurxtoRc24) warns about this “rabbit hole” tendency (building a guitar instead of playing one). DevOps, for example, feels like a distraction, but I still need some of it to deploy my app. Too many ways to talk to computers Every layer of abstraction adds choice and complexity. Uncle Bob (https://www.youtube.com/watch?v=P2yr-3F6PQo) argues for unifying notation and praises Clojure. Scicloj is doing this with https://github.com/scicloj/kindly. The tools dilemma I want tools that boost productivity and reduce cognitive load (e.g., Datomic, Tailwind). But abstractions also carry hidden costs. Datomic is a good example: Magnar Sveen (https://www.youtube.com/watch?v=YSgTQzHYeLU) explains its power, especially its clean definition of a fact. Yet I’ve lost countless hours just figuring out how to use and deploy it. This is the trade-off: tools help us think, but they also complicate our systems. Clojure has immutability because our brains can only hold ~7 items in working memory. Function calls exist because we can’t understand an entire program at once. Frameworks like Vue or React fit human cognition better than raw JavaScript, but still add layers of complexity. Do you move closer to the machine (C, bare metal) for control, or embrace higher-level tools (spreadsheets, Clojure, re-frame) that could be considered tools of thought? Lock-in, fragility, and limits I could probably build many teacher tools in a spreadsheet, with lots of functionality out of the box. But then I’m locked into ecosystems like Google Sheets, with limits on what I can ultimately do. Uncle Bob (https://www.youtube.com/watch?v=evmZTh7l6UE) points out how frameworks can trap you this way too. Hosting and cost barriers I don’t want to pay to host apps. As a hobbyist, the model feels backward: I make something useful, then I pay for others to use it. Free tiers are also awkward because you almost don’t want your app to succeed beyond the free plan. And charging money means payment systems, business setup, and more complexity. Ethical and economic questions Some argue there are ethical issues in running other people’s code. Richard Stallman (https://www.youtube.com/watch?v=Ag1AKIl_2GM) rejects SaaS outright. Yanis Varoufakis (https://www.abc.net.au/listen/programs/latenightlive/yanis-varoufakis-technofeudalism-capitalism/102880204) describes cloud companies as rent-seekers. Yet cloud convenience is real. I like watching YouTube on my computer, then continuing on my phone seamlessly. The dark ages of software Peter van Hardenberg (https://www.youtube.com/watch?v=0bjAI57_cDk) argues we live in the “software dark ages” because many cloud products vanish, leaving little behind. Google’s long list of killed projects (http://killedbygoogle.com) shows how ephemeral much of this work is. Maybe one solution for me is to watch less YouTube 😀 I also think portal and clerk use the browser in a cool way as like half browser apps.

liebs 2025-09-23T05:54:33.776379Z

As for your point about side quests, I don't agree. When you as a single person are building apps as a hobby, or you are even a one-person business operation, it's my belief that nothing is a side quest. Wrt web development in particular, having a functional and pleasant UI for the visitor of your site is of the same importance as, for instance, making sure the connection to that site is encrypted. Just because those two things would be addressed by two different people in a corporation or market doesn't mean they must be: that's an a priori commitment to division of labor principles that don't necessarily apply in the context of a solo project

2025-09-22T19:30:52.717219Z

Trying to convert below Java code to Clojure. My attempt:

(defn get-next
  "Count rightmost 0s and 1s of the binary"
  [n]
  (let [[c c0 c1] (loop [c n, c0 0]
                    (if (and (zero? (bit-and c 1)) (not (zero? c)))
                      (recur (bit-shift-right c 1)
                             (inc c0))
                      [c c0 0]))]
    (loop [c c, c1 c1]
      (if (= (bit-and c 1) 1)
        (recur (bit-shift-right c 1)
               (inc c1))
        [c c0 c1]))))

(comment
  (get-next 13948) ;; => [108 2 5]
  )
Is there a better (more elegant, concise, idiomatic etc) way to express this logic in Clojure?

hrtmt brng 2025-09-23T09:16:20.032799Z

For me this looks like you have two loops in one loop and a variable zeros-counted?, which connects these two loops. This is like imperative programming. First do this, then this. But in contrast to imperative programming, this sequence is not directly visible any more in your code. This is stateful programming, where stateless would be possible. This sequence comes from your problem, so in the code it should be visible, that you have a seqence of calculations. Also I would try not to use one cond for the false case and another cond for the true case. Both cases should be one code, because they do the same thing. They both count bits from the right. In my opinion it is worth having an extra function for this.

(defn count-bits-from-right [n bit]
    (loop [result 0 num n]
        (if (or (zero? num) (= bit (even? num)))
            [result num]
            (recur (inc result) (quot num 2)))))

(defn get-next [n]
    (let [[number-of-zeros n-without-zeros] (count-bits-from-right n true)
          [number-of-ones remaining-n] (count-bits-from-right n-without-zeros false)]
        [number-of-zeros number-of-ones remaining-n]))

2025-09-23T09:20:37.639289Z

@hbrng.computer thanks for the explanation and the solution

hrtmt brng 2025-09-23T09:24:54.139469Z

If you prefer your names:

(defn get-next [n]
    (let [[c0 c] (count-bits-from-right n true)
          [c1 c] (count-bits-from-right c false)]
        [c0 c1 c]))
Also nice.

👍 1
2025-09-23T11:45:20.114019Z

you can also play with lazy sequences to avoid loops :

(defn foo [n]
  (if (zero? n)
    [0 0]
    (let [[zeroes ones] (->> (iterate #(quot % 2) n)
                             (map even?)
                             (partition-by true?))]
      [(count zeroes) (count ones)])))

👀 1
schadocalex 2025-09-23T14:12:41.565879Z

@jpmonettas you forgot the zero case (infinite seq!) Another approach with seqs, keeping bit functions (I think it's better than even? or quot for manipulating bits):

(defn get-next [n]
  (if (zero? n)
    [n 0 0]
    (let [c0 (first
              (filter #(bit-test n %)
                      (range)))
          c1 (first
              (filter #(not (bit-test n (+ c0 %)))
                      (range)))]
      [(bit-shift-right n (+ c0 c1)) c0 c1])))

🔥 1
2025-09-23T14:15:38.925699Z

@schad.alexis true, fixed!

2025-09-22T19:33:49.526479Z

I think this can be done in one loop. Let me try...

phronmophobic 2025-09-22T19:35:23.584169Z

Looks fine to me. Numerics can be verbose in clojure. It's totally fine to write some java classes with static methods for this kind of thing. If you're doing a lot of this kind of stuff, it's also possible to write a DSL that let's you express the algorithm close to how you think about the problem. It's usually overkill for just a handful of functions though.

👍 2
💯 1
2025-09-22T20:14:18.294189Z

if that is as the docstring says "Count rightmost 0s and 1s of the binary" I think it is wrong since :

(foo 13948) ;; => [108 2 5]
which is 2 zeroes and 5 ones, which is different from :
(Long/toBinaryString 13948) ;; => "11011001111100"

(->> (Long/toBinaryString 13948)
     frequencies) ;; => {\1 9, \0 5}
For that counting you could also do something like :
(defn foo
  ([n] (foo n 0 0))
  ([n c0 c1]
   (if (zero? n)
     [c0 c1]
     (if (even? n)
       (recur (quot n 2) (inc c0) c1)
       (recur (quot n 2) c0 (inc c1))))))

phronmophobic 2025-09-22T20:18:32.115149Z

If you're doing bit twiddling, clojure's https://clojure.org/reference/reader#_literals has support for binary literals:

> 2r11011001111100
13948

👍 1
2025-09-22T20:21:56.920729Z

which is 2 zeroes and 5 ones, which is different fromI need to count 0s and 1s on the right from the rightmost non-trailing zero which is a bit 7. So I need to count these 0s and 1s (marked bold): 1101100*1111100*

👍 1
2025-09-22T20:23:53.724319Z

my second version - single loop instead of two:

(defn get-next [n]
  (loop [c n, c0 0, c1 0, zeros-counted? false]
    (cond
      (and (not zeros-counted?) (even? c) (not (zero? c)))
        (recur (quot c 2) (inc c0) c1 false)
      (odd? c)
        (recur (quot c 2) c0 (inc c1) true)
      :else [c c0 c1])))

2025-09-22T20:36:43.241979Z

@jpmonettas thanks for the tip with quot and even? instead of the verbose bit- versions

1