Live data from Hacker News

Try Clojure

tryclojure.org

131–140 of 404 posts

Re: Try Clojure

#131
post #79

Earlier quoted context omitted.

As a side note, I'm always amazed by people who can use a highly expressive language (Clojure, Rust, even TS) but switch to Go when they feel like it (especially pre-1.18). To me, switching to a less expressive language is painful and infuriating. I remember having to switch from Python to Java 5, and how everything started to take 3 to 5 times longer code to express. Maybe the key thing is to only write small things…

I must use PyTorch for ML, but badly wish I could use TypeScript instead. Its type system demolishes anything else I've used. Writing anything from SPI communications, to server logic, to rich GUIs is a breeze. Then I work in Python and have to fight `Any`s, ambiguous arguments, more verbose code, and bizarrely slow run times. It makes me sad.

While I agree with you, Python is getting better in this regard, as is mypy.

Making historical interfaces well-typed is another kettle of fish. For instance, Python's range() is actually two overrides with incompatible signatures. I bet PyTorch has more of such examples.

Re: Try Clojure

#132
post #97
post #79

Earlier quoted context omitted.

As a side note, I'm always amazed by people who can use a highly expressive language (Clojure, Rust, even TS) but switch to Go when they feel like it (especially pre-1.18). To me, switching to a less expressive language is painful and infuriating. I remember having to switch from Python to Java 5, and how everything started to take 3 to 5 times longer code to express. Maybe the key thing is to only write small things…

Java has had lambdas, map/filter/reduce and other stuff for like ten years or so I think. It's a bit chatty for sure, but that's what you want sometimes, the verbosity can make it easier for newcomers to make changes and additions. To me it isn't more infuriating than learning a new language or practicing one I'm not very good at. There's a bit of friction, but that's common when I'm developing in the languages I'm m…

Java 7 is already pretty pleasant, and it has been improving ever since then.

(Say what you want about Oracle Corporation, but as the wardens of Java they're doing a pretty good job.)

Re: Try Clojure

#133

Earlier quoted context omitted.

[flagged]

"Typical convention" is also an Appeal to Tradition. Different traditions though! Incidentally, people used to be /really/ against Python because of its use of significant whitespace. The number of people who bounced off it because they would try to type something and then get a mysterious syntax error that turned out to be that they didn't type space or tab the right amount of times! It used to be what people would…

I still, to this day, think that significant whitespace in Python is an awful design choice. I avoid it for that reason.

Re: Try Clojure

#134
post #57

If you are new to Clojure and would like to experiment with it in a way that is immediately useful, I highly recommend the Babashka runtime for scripting [0]. It's very fun, approachable, and one of the more polished parts of the Clojure ecosystem. It's a particularly good entry point because unlike full-JVM Clojure it has a very fast startup time. Newcomers can use any file-watching /reloading tools (e.g. nodemon) t…

The best part about Babashka is that it's really batteries included nowadays. I had to make a little UI to display some stats about an app at work, and decided to try using it with HTMX. Turned out to be a really good experience. Babashka has pretty much everything you need for a basic web app baked in, and HTMX lets you do dynamic loading on the page without having to bother with a Js frontend.

Best part is that bb can start nREPL with `bb --nrepl-server` and then you can connect an editor like Calva to it and develop the script interactively. Definitely recommend checking it out if you need to make a simple web UI. Here's an example of a full fledged web app:

    #!/usr/bin/env bb
    (require
     '[clojure.string :as str]
     '[org.httpkit.server :as srv]
     '[hiccup2.core :as hp]
     '[cheshire.core :as json]
     '[babashka.pods :as pods]
     '[clojure.java.io :as io]
     '[clojure.edn :as edn])
    (import '[java.net URLDecoder])

    (pods/load-pod 'org.babashka/postgresql "0.1.0")
    (require '[pod.babashka.postgresql :as pg])

    (defonce server (atom nil))
    (defonce conn (atom nil))

    (def favicon "data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA/H8AAPxjAAD/4wAA/+MAAMY/AADGPwAAxjEAAP/xAAD/8QAA4x8AAOMfAADjHwAA//8AAP//AAA=")

    (defn list-accounts [{:keys [from to]}]
      (pg/execute! @conn
                   ["select account_id, created_at
                     from accounts
                     where created_at between to_date(?, 'yyyy-mm-dd') and to_date(?, 'yyyy-mm-dd')"
                    from to]))

    (defn list-all-accounts [_req]
      (json/encode {:accounts (pg/execute! @conn ["select account_id, created_at from accounts"])}))

    (defn parse-body [{:keys [body]}]
      (reduce
       (fn [params param]
         (let [[k v] (str/split param #"=")]
           (assoc params (keyword k) (URLDecoder/decode v))))
       {}
       (-> body slurp (str/split #"&"))))

    (defn render [html]
      (str (hp/html html)))

    (defn render-accounts [request]
      (let [params (parse-body request)
            accounts (list-accounts params)]
        [:table.table {:id "accounts"}
         [:thead
          [:tr [:th "account id"] [:th "created at"]]]
         [:tbody
          (for [{:accounts/keys [account_id created_at]} accounts]
            [:tr [:td account_id] [:td (str created_at)]])]]))

    (defn date-str [date]
      (let [fmt (java.text.SimpleDateFormat. "yyyy-MM-dd")]
        (.format fmt date)))

    (defn account-stats []
      [:section.hero
       [:div.hero-body
        [:div.container
         [:div.columns
          [:div.column
           [:form.box
            {:hx-post "/accounts-in-range"
             :hx-target "#accounts"
             :hx-swap "outerHTML"}
            [:h1.title "Accounts"]
            [:div.field
             [:label.label {:for "from"} [:b "from "]]
             [:input.control {:type "date" :id "from" :name "from" :value (date-str (java.util.Date.))}]]
    
            [:div.field
             [:label.label {:for "to"} [:b " to "]]
             [:input.control {:type "date" :id "to" :name "to" :value (date-str (java.util.Date.))}]]
    
            [:button.button {:type "submit"} "list accounts"]]
           [:div.box [:table.table {:id "accounts"}]]]]]]])
    
    (defn home-page [_req]
      (render
       [:html
        [:head
         [:link {:href favicon :rel "icon" :type "image/x-icon"}]
         [:meta {:charset "UTF-8"}]
         [:title "Account Stats"]
         [:link {:href "https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css" :rel "stylesheet"}]
         [:link {:href "https://unpkg.com/todomvc-app-css@2.4.1/index.css" :rel "stylesheet"}]
         [:script {:src "https://unpkg.com/htmx.org@1.5.0/dist/htmx.min.js" :defer true}]
         [:script {:src "https://unpkg.com/hyperscript.org@0.8.1/dist/_hyperscript.min.js" :defer true}]]
        [:body
         (account-stats)]]))

    (defn handler [{:keys [uri request-method] :as req}]
      (condp = [request-method uri]
        [:get "/"]
        {:body (home-page req)
         :headers {"Content-Type" "text/html charset=utf-8"}
         :status 200}

        [:get "/accounts.json"]
        {:body (list-all-accounts req)
         :headers {"Content-Type" "application/json; charset=utf-8"}
         :status 200}

        [:post "/accounts-in-range"]
        {:body (render (render-accounts req))
         :status 200}

        {:body (str "page " uri " not found")
         :status 404}))

    (defn read-config []
      (if (.exists (io/file "config.edn"))
        (edn/read-string (slurp "config.edn"))
        {:port 3001
         :db {:dbtype   "postgresql"
              :host     "localhost"
              :dbname   "postgres"
              :user     "postgres"
              :password "postgres"
              :port     5432}}))
    
    (defn run []
      (let [{:keys [port db]} (read-config)]
        (reset! conn db)
        (when-let [server @server]
          (server))
        (reset! server
                (srv/run-server #(handler %) {:port port}))
        (println "started on port:" port)))

    ;; ensures process doesn't exit when running from command line
    (when (= "start" (first *command-line-args*))
      (run)
      @(promise))

    (comment
      ;; restart server 
      (do
        (when-let [instance @server] (instance))
        (reset! server nil)
        (run)))

Re: Try Clojure

#135

Earlier quoted context omitted.

This is of course a popular opinion and pretty much the C++ philosophy. Unfortunately a lot of the benefits of functional programming cannot be fully realised unless you are "all in". The paper "The Curse of the Excluded Middle" is somewhat blunt but it makes some good points: https://queue.acm.org/detail.cfm?id=2611829

Programming without immutability is so painful to me, no matter how many functional features a language adds.

In my experience jumping from a Clojure shop to a large Java shop a few years ago, the benefits of persistent data structures are overstated. Mutable collections are just as good for 99% of use cases. And they're a lot faster (in single threaded code) and occasionally mutation makes things easier.

The selling point of Clojure is that persistent data structures prevent several classes of bugs (unintended mutation, locking, etc.). But in reality -- as long as your team members are good enough programmers -- I don't see these kind of bugs happen in practice.

That being said I love Clojure and the standard library is the best out of any language. It is a great choice in the small market of "projects that need simple and correct code".

Re: Try Clojure

#136
post #22

Earlier quoted context omitted.

In my experience, the subsets of the community around clojure were one of the biggest problem with it. It's gotten better over the years, but circa 2016 - 2018 it had a spike in popularity despite being very hostile to newcomers. There was an almost apologist attitude towards rough edges or failure modes folks new to the community would fall into. I managed to work with it for a time despite that, but it left me with…

> ...had a spike in popularity despite being very hostile to newcomers. To be fair the community itself, as humans, was very welcoming to newcomers. Really super nice and helpful folks. If anything was "hostile" it was the dominance of emacs and very limited options for other tooling. That, and the horrifically unhelpful leaky abstractions in the stack trace when something went wrong. It's a big step, if you don't al…

I'll second that the popular tooling, while powerful and reliable, its just not user friendly.

The official Clojure CLI, for example, is just plain confusing, and that's most people's first impression to the entire ecosystem. The config files, while they use the wonderful `.edn` format, are also not intuitive. Newcomers are the most likely people to encounter the most amount of error messages... which are famously difficult to read.

And that's before you even get into REPL configuration, which involves coordination with your editor process, a client process, and a server process. Even if you have a tool like Calva or CIDER managing it for you, you'll still get confused when something goes wrong unless you grok all the moving parts.

Even if you figure that all out, you still don't have Parinfer or equivalents setup yet in your editor. Also, clojure-lsp tends to require some configuration to get working the way you want. And that's before you get started with ClojureScript, which brings the complexity to another level entirely.

Despite all this, I love Clojure. It's an expert's language, even though it shouldn't have to be. Once you learn this stuff, you respect why much of this complexity exists. It's inherent to the amount of power the language gives you.

But doesn't mean we can't make it easier to use and get started with.

Re: Try Clojure

#137

I know many people will have problems with this post, but I am posting as information for clorjure-ophiles. Not reasons, but personal reasons why I don't do Clojure and will not try it. 1. When I read about Clojure (repeatedly) I like it. I am especially interested in transducers. I even built a half baked transducer engine in JavaScript using generators. There is clearly something here of value here. Any system that…

I also cannot stand Java (not the technology, but everything surrounding it - bean factories, J2EE, Tomcat, war files, maven, insane unhinged class paths etc) but honestly one of the reasons Clojure is amazing is that it allows you to exist inside of the Java ecosystem while also being pretty isolated and walled off from it. It's all the good parts of Java without all the bad annoying parts.

I would urge you to push thru the PTSD and give it another shot.

There is also Clojurescript, which runs on Javascript, and Babashka which is a lighter weight implementation of Clojure for fast startup times that is targeted at things like shell scripts or system programs. https://babashka.org/

Re: Try Clojure

#138
post #116

Clojure looks productive. What frameworks libraries do people use to build web apps? Like, replacement for Django view layer and ORM (not looking to debate orms thanks)?

There is not a batteries included framework a-la Django or Rails for Clojure. I would argue that is one of the biggest pitfalls to the language/ecosystem. If you are building a web application, you will need to invent the entire thing yourself. There are micro frameworks and tools that exist standalone to deal with HTTP, DB ORM, etc... but that will be an exercise left to the reader.

Some will say this is a good thing (and as a very experienced engineer I will agree) but the barrier to entry for a newbie is quite high in this regard. You have all the rope to hang yourself with - and you will. Especially if you are on a team full of lots of interns and junior folks.

Re: Try Clojure

#139
post #22

Earlier quoted context omitted.

In my experience, the subsets of the community around clojure were one of the biggest problem with it. It's gotten better over the years, but circa 2016 - 2018 it had a spike in popularity despite being very hostile to newcomers. There was an almost apologist attitude towards rough edges or failure modes folks new to the community would fall into. I managed to work with it for a time despite that, but it left me with…

> ...had a spike in popularity despite being very hostile to newcomers. To be fair the community itself, as humans, was very welcoming to newcomers. Really super nice and helpful folks. If anything was "hostile" it was the dominance of emacs and very limited options for other tooling. That, and the horrifically unhelpful leaky abstractions in the stack trace when something went wrong. It's a big step, if you don't al…

This matches my experience as an amateur programmer. The initial hill was steep, but I don't think it was because people weren't friendly.

There is one cultural thing that might be confused with unfriendliness. Sometimes people react badly if someone posts incorrect information. But I think that's good. When you search for information about Python or PHP you have to wade through quite a bit of junk. Ironically, it's sometimes easier to find correct answers for Clojure.

Clojure itself is very clean and consistent, it's got a lot of polish to it, which makes it comparatively easy to learn. And there isn't that much of it. But for a long time the tooling was hard.

That's far less of a problem than it used to be. deps.edn and shadow-cljs both made things easier, as has Cursive. People say nice things about Calva, but I don't know it.

I'm a big fan. Babashka alone is enough to make learning Clojure worthwhile. Also, for someone like me, it's kind of nice that it feels almost finished. Once you learn it, you know it, and now that the tooling has settled down a bit you don't have to keep running to keep up.

Re: Try Clojure

#140
post #57

If you are new to Clojure and would like to experiment with it in a way that is immediately useful, I highly recommend the Babashka runtime for scripting [0]. It's very fun, approachable, and one of the more polished parts of the Clojure ecosystem. It's a particularly good entry point because unlike full-JVM Clojure it has a very fast startup time. Newcomers can use any file-watching /reloading tools (e.g. nodemon) t…

The best part about Babashka is that it's really batteries included nowadays. I had to make a little UI to display some stats about an app at work, and decided to try using it with HTMX. Turned out to be a really good experience. Babashka has pretty much everything you need for a basic web app baked in, and HTMX lets you do dynamic loading on the page without having to bother with a Js frontend. Best part is that bb…

I agree. It's a breath of fresh air in the Clojure world. I'm grateful to thoughtful builders like yourself and borkdude for bringing the language to new heights.
Post reply on HN