so I’m trying to learn #clojure and would love some advice
the way that I usually write code is to define exactly what type of data each function expects, and exactly what type of data it returns. (like Python’s type annotations or Racket’s contract system) I find that very helpful for keeping track of what my functions are for, and for making sure that I’m using them correctly (this is especially helpful for quickly finding incorrect function calls after I refactor a function)
ideally I’d like to have either static analysis, or a runtime error be thrown if my functions aren’t being called correctly (or if they’re returning invalid data)
it looks like Clojure has a few different options for doing something like this:
do y’all have a recommendation for which I should use, and maybe a link to a guide on how to use it? I’m ngl the documentation that I’ve found for all of these so far intimidates me a lot lol, so I would love something simpler
@kasdeya I suppose Malli is more popular and similar to Pydantic, but I used clojure-spec.
@kasdeya I mostly use malli as well. There is a performance penalty if you check for every function call, I mostly use that for incoming or outgoing API calls.
@kasdeya one thing worth considering: try to keep your schema declarations separate from your function definitions. ideally your function code stays plain idiomatic Clojure, and the schemas live alongside it (or even in a separate namespace).
if your functions stay "normal," you can use standard Clojure tooling like clojure-lsp, editor's jump-to-definition, etc. — all of which just works out of the box. once you start adding schema macros into your defns, some of that tools can get confused.
@kasdeya from that angle, malli and spec are better choices than schema, since they make it more natural to define schemas independently of your functions.
that said, schema can be more powerful in certain cases — for example if you need to define schemas for protocol definitions. so it's not that schema is bad, it just takes a different approach.
@kasdeya for getting started I'd lean toward malli — it has a nice data-driven syntax that feels less intimidating than spec, and the separation between "here's my function" and "here's what it expects" stays clean.
@kasdeya go with malli, you can also use it at development time with clj-kondo: https://github.com/metosin/malli?tab=readme-ov-file#clj-kondo.
@kasdeya I usually start without malli. You can annotate functions with type hints.
(defn my-function [^String x] ...) to document stuff, its only for the compiler ... no runtime check but it helps. Even better is a docstring.
(defn my-function
"Expects a string. Returns whatever"
[x] ...). Your future self will thank you.
@mdallastella ooh that’s incredible! that’s honestly ideal for me, and I never would’ve guessed that clj-kondo would have integration for a third-party library like this
@kasdeya This might be difficult advice, but Clojure tends to be much less driven by types. The options like schema/malli/specs are most often used at your code's boundaries, and less so internally. It's certainly a philosophical shift in writing code. Maybe check out some of Rich Hickey's talks. I think this one gets at the heart of what you are looking for:
https://youtu.be/YR5WdGrpoug?t=1026
For most simple uses, `fn` and `defn` offer pre- and post- checks:
https://clojure.org/reference/special_forms#_fn_name_param_condition_map_expr_2
https://clojureverse.org/t/using-pre-and-post-for-error-handling-and-type-checking/7590
For static analysis, try clj-kondo. It's an amazing tool.
The Clojurians Slack is notoriously friendly, so it's a good place to hang around as you learn!
@Nundrum thank you for all of this info! this is all super helpful to have
I’ve actually watched that specific Rich Hickey talk before (it’s the only one of his that I’ve watched) and I think I understand at least somewhat the concepts that he’s talking about, and I think they’re pretty interesting too. I definitely want to experiment with writing code how he’s suggesting and see if I like that style
also, it’s great to know that there’s a Slack! I might join if I can work up the courage lol, since I’m pretty shy. but it’d be great to talk to folks who really know the language and can answer my questions about the {best / most idiomatic} ways to do things in Clojure
@kasdeya I'm a big fan of Spec since it's the official #Clojure core team's solution. Here's how we've been using it at work: https://corfield.org/blog/2019/09/13/using-spec/