beginners

TypeScripter 2025-09-03T13:15:58.183459Z

After 14 years of software engineering experience, I started experimenting a little and reading about Lisps and Clojure. I got a feeling that this is the right way of programming. Before I dig deeper however, I wanted to ask if it is possible to setup typing and an editor/ide so that I get immediate type hints, while typing? Just like one gets with TypeScript in VSCode. I intend to do web development in Clojure/ClojureScript. (utilisng Reagent and cljs node)

p-himik 2025-09-03T13:18:11.154639Z

What exactly do you mean by "type hints"?

TypeScripter 2025-09-03T13:21:13.964659Z

When I pass an argument of incorrect type to a function, I would like to have it underlined in red. With an optional pop up indicating the type error/mismatch. I would like to also have auto completion based on the specified types. Type inference supporting both above would also be nice.

p-himik 2025-09-03T13:24:08.641209Z

Highlighting incorrect types - only in some very narrow cases with external tooling like clj-kondo. I think Calva has it built-in. Auto-completion - only when you're doing interop, and also in very limited cases. Cursive is better here. But mostly in interop with Java, not with JS. Type inference - maybe Clojure Typed, but I haven't used it myself so no actual idea. Clojure is a dynamically typed language. It's very uncommon to put types around everything. Most of your values will be plain vectors/maps/seqs anyway.

👍 1
gaverhae 2025-09-03T13:24:44.189349Z

Clojure is not a typed language; from a type theory perspective, it's "untyped", or has a single "Value" type. Which means what you're asking for here is unfortunately not possible. Clojurists typically think what they get in return for giving that up is worth it, but you'll have to make your own judgment on that. The way we work around this issue is with tests and a REPL. It's a lot better than it sounds, though, because (iodiomatic) Clojure encourages using a very small number of types and not defining new ones. So confusion on types is actually pretty rare in practice. It's a pretty big mental shift, though, if you're used to nominal type creation as a default way of organizing the world.

TypeScripter 2025-09-03T13:28:47.292619Z

What about being informed by the editor/ide that a supplied map to a function does not match what the function expects, as well as autocomplete for this?

p-himik 2025-09-03T13:34:18.682929Z

That's not supported. As gaverhae said, usually you write a bunch of code leaning on your mental model and pretty much immediately and in an almost frictionless manner run it in a REPL with a single keystroke. The functions tend to stay relatively small, focused around specific bits of the data. So suppose you're doing some event counting and you care about event types and dates - well, you use just those two fields then and completely ignore the rest. None of the type hinting will help you here since there's nothing to help you with, it's already trivial. If there are some huge maps being passed around, there could be a spec/schema somewhere that you consult to see what keys it can have and what properties the corresponding values must satisfy (not quite "types" as TS sees them - arbitrary predicates with the most commonly used things being easily expressed with a single token).

gaverhae 2025-09-03T13:41:08.861099Z

Taking perhaps a little bit of a step back. What does this type-checking and IDE integration give you, really? From my perspective, it gives you three things: • Discoverability. If you don't know what methods TypeX has, being able to type value_of_type_x. and see what the IDE suggests is very useful. This is very low value in most Clojure code because we have about 11 types we all regularly use, they're the same types, and their operations are covered by the standard library, and you very quickly get to just know them. • Typo avoidance. Using auto-complete means no typo. That's nice. What "static typist" typically fear on this point is that they'll have a production bug because of a typo in a keyword. Clojure goes a bit further than most dynamic languages here as all of your symbols are checked for existence at compile-time; for keywords, well, you have to rely on tests and REPL-driven development. I can't give you a guarantee, but I can tell you that in practice it's not a problem I've seen. • Time saving. Using auto-completion is faster than typing things out. That's imo fairly minor, but there are still various ways your IDE can help you in Clojure, as most core functions are short, and non-core functions you'll be using come from namespaces you've imported. You can still have auto-complete on namespace prefixes, and it's relatively easy in most editors to set up auto-completion of keywords based on which keywords already exist in the current document (also helping a bit with the typo issue).

gaverhae 2025-09-03T13:43:29.984819Z

Going a bit further than what you're asking for, there are genuine cases where types are hard to follow, because you have a complex combination of them. Is this function returning a list of lists of lists, or just a lis of lists? Is this vector meant as a collection, or a cheap way to group values together? Ideally, you learn to structure your data and your code in such a way that these do not happen (e.g. always use a map for grouping attributes, instead of positional vectors; prefer flat maps over deeply nested structures, etc.), but sometimes it does happen and needs a bit of REPL-driven debuggiing.

Samuel Ludwig 2025-09-03T13:43:54.067809Z

(In addition to everything above) I'm wary to recommend too many third-party libraries to a newcomer, but if you really really feel you still want something like concrete type-scripty data-types after some soul-searching, there is Metosin's Malli library, it has some setups that'll let you do something like what your talking about wrt 'type-checking' (but not the autocomplete thing). I would say try to live without it first, but Malli is a very solid library, especially for bigger projects with complicated data-structures.

gaverhae 2025-09-03T13:44:55.280409Z

I would strongly encourage you to view malli as a schema-checking function for incoming external data, rather than a type-checking system for your own code, but it is an option.

5
James Amberger 2025-09-03T15:21:01.354259Z

Take the plunge man. There are a lot of resources on the nuts and bolts—in addition to those I recommend https://leanpub.com/elementsofclojure which I think 1) is great and 2) speaks to your concern with typechecking

Ryan A 2025-09-03T16:24:49.421299Z

Please take my contribution to this conversation with a grain of salt as I'm new to Clojure and a hobbyist programmer at best. Eric Normand was on Richard Feldman's podcast, Software Unscripted in April of 2023 discussing types and Clojure. The title is "Haskell and Clojure in Production" the basic question was: Why doesn't Eric get more type errors in Clojure? Eric Normand's name seems to come up often in the Clojure community. He's written a few books and offers a Clojure course as well. Here's the transcript of that conversation. You can play the podcast from this page too. https://ericnormand.me/speaking/software-unscripted

pez 2025-09-03T16:43:04.989159Z

We should be able to infer a bit more than what we do today, but so far at least Calva doesn’t have any of this this. But there are thousands of us using Calva and Clojure daily and I think most of us are super happy. 😀 (I’m biased, as the creator of Calva) As for reagent. You may want to at least have a look at #replicant React-ish without React. There’s some quite amazing videos on YouTube to get the urge to try it!

gaverhae 2025-09-04T06:33:42.445549Z

This: https://www.youtube.com/watch?v=AGTDfXKGvNI is a very good introductory overview of what it may feel like to work on a frontend with replicant. It's not just replicant itself; Christian has built a pretty nice ecosystem around it. More videos are available on Christian's YouTube channel: https://www.youtube.com/@replicant-clj. I've also found the official docs pretty well done: https://replicant.fun.

1
1
practicalli-johnny 2025-09-04T10:07:02.194579Z

Clojure programming is like Back to the Future: "Were were going you wont need (to specify) types" Clojure LSP can be used with editors to provide navigation, autocomplete and refactor tools. A Clojure REPL is the most powerful tool, as you can run any piece of the code and get feedback. Therefore it is easy call a function and see what it returns, or pull apart the code in a function and see what that does. As mentioned, maps and other data structures are typically used as arguments. Specifications can be defined for data and function arguments using clojure.spec or mali. Specifications are included in error reports from the repl, so evaluating code gives instant feedback. Test runners like Kaocha can check also check if functions are called correctly (and check return values are of expected types) If using Interop with Java libraries, there may be a case for specifying some type hints for performance or ensuring values are a specific type.

TypeScripter 2025-09-04T15:25:24.917829Z

Thank you everyone for your time and input

🙏 1
TypeScripter 2025-09-05T15:01:46.240289Z

metosin/malli appears to provide optional type checking that I would like to have in order to pursue clojure/script further. At the moment I need to run

(require '[malli.clj-kondo :as mc])
(-> (mc/collect *ns*) (mc/linter-config))
(mc/emit!)
in order for kondo-clj to pick it up. Is there some sort of default way of automating this? So I can just type and have Calva/kondo-clj automatically recognise the occasional type annotations I make?

Samuel Ludwig 2025-09-05T15:05:03.150739Z

mm, #malli might have something for you, but otherwise, my only use of it has been via the "`dev-mode`"

TypeScripter 2025-09-05T15:07:21.545859Z

Sorry, I don't understand what exactly you mean by >my only use of it has been via the "`dev-mode`"

Samuel Ludwig 2025-09-05T15:08:10.983929Z

https://github.com/metosin/malli#development-mode <- this here

👍 1
ChillPillzKillzBillz 2025-09-03T15:10:11.194859Z

Hello, I am still struggling to have a separate websocket server and client working. My overall code so far is as follows:

(ns tstwebsock.WebsocketServe
  (:require [clojure.core.async :as async
             :refer [<! <!! >!! close! chan
                     mult tap untap]]
            [org.httpkit.server :as http]
            [org.httpkit.client :as httpcl]
            [hato.websocket :as ws]
            ))

 ;; async channel for sending data to websockets
 (def livedata_chan (chan 20))
 (def livedata_mux_chan (mult livedata_chan))

 ;; websocket server and routes
 (def app (fn [req]
            (case (:uri req)
              "/ws" (http/as-channel
                     req
                     {:on-receive (fn [ch message] (http/send! ch "Echoing back : " message))
                      :on-close (fn [ch status-code]
                                  (untap livedata_mux_chan ch)
                                  (println "Closed with code: " status-code))
                      :on-open (fn [ch]
                                 (println "Got a connection!")
                                 (tap livedata_mux_chan ch)
                                 (http/send! ch (str {:status "connected"})))})
              "/home" {:status  200
                       :headers {"Content-Type" "text/html"}
                       :body    "hello HTTP!"})))

 (defonce server (atom nil))

 (defn start! []
   (when-not @server
     (reset! server (http/run-server #'app {:port 8080}))))

 (defn stop! []
   (when-not (nil? @server)
     (@server :timeout 100)
     (reset! server nil)))

 (start!)
 (stop!)

 ;; ============================================ websocket client =============================================
 (def soc @(ws/websocket ""
                         {:on-open (fn [w]
                                     (ws/send! w "Connect")) ;;

                          :on-message (fn [w msg last?]
                                        (let [ret (.toString msg)]
                                          (prn ret)))
                          :on-error (fn [w err]
                                      (prn "Error from Socket : " err))
                          :on-close (fn [w status reason]
                                      (prn (str "Websocket Closed! with reason " reason " and status : " status)))}))

 (ws/close! soc)
On executing all the code without closing the websocket in the last line I get the following output
clj꞉꞉> 
; Got a connection!
; Closed with code:  ":server-close{
; :status \"connected\"}"
; "Echoing back : "
; "Websocket Closed! with reason  and status : 1000"
The question is why is the websocket connection not staying open and immediately closing? Is this because I have something wrong in my handlers? There is so few examples available about these aspects online on websockets. I really appreciate all the help!

p-himik 2025-09-03T15:22:48.900239Z

For future reference, please attach long code fragments as snippets. They have syntax highlighting and are collapsible, making it easier to scroll to the past messages.

p-himik 2025-09-03T15:30:24.297099Z

As for the issue - take a close look at this form: (http/send! ch "Echoing back : " message). And check out the docstring of the function.

ChillPillzKillzBillz 2025-09-03T15:33:41.064249Z

Aaah ok I got it. On changing the line you highlighted to

:on-receive (fn [ch message] (http/send! ch (str "Echoing back : " message)))
the error disappeared and the connection stays open! Thanks @p-himik for the insight. I've been at it for 2 days on this now. I really appreciate it!

👍 2